C. Paint
题目描述
给你一个长度为 \(n\) 的颜色数组,每次可以选择一个位置修改它的颜色,此时与他相邻的极长连续相同颜色段也会改变颜色,问把所有位置变同色的最小操作次数。
\(n\leq 3\cdot 10^3\)
解法
因为每次操作的是一个极长同色连续段,所以可以考虑用区间 \(dp\)
考虑暴力操作需要用 长度\(-1\) 步,但是形如 \(aba\) 操作中间可以少用一步,所以设 \(dp[l][r]\) 表示把区间 \([l,r]\) 染成同色的最大减少操作次数,最后的答案是 \(n-1-dp[1][n]\)
转移我们需要以减少操作为导向,所以考虑枚举 \(a...a\) 这种情况,我们找到所有 \(s[i]=s[l](l<i\leq r)\):
\[dp[l][r]\leftarrow dp[l+1][i-1]+1+dp[i][r] \]为什么是 \(dp[i][r]\) 呢?注意我们的状态定义中并不涉及最终颜色,但是不难观察到:可以通过最小操作步数使一个区间变成初始时它边界上的颜色,所以这两段就可以合并了。
还有一种简单的情况是直接继承,不操作:
\[dp[l][r]\leftarrow dp[l+1][r] \]暴力转移时间复杂度 \(O(20n^2)\)
总结
\(dp\) 状态定义注意 \(\min,\max\) 的转化,这道如果不换成 \(\tt max\) 就不好用到相同颜色数量有限的条件,因为 \(\max\) 是以减少步数为导向的,这是一种正难则反的思想。
//Take me to the top , I am ready for...
#include <cstdio>
#include <vector>
#include <cstring>
#include <iostream>
using namespace std;
const int M = 3005;
int read()
{
int x=0,f=1;char c;
while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
return x*f;
}
int T,n,a[M],dp[M][M];vector<int> b[M];
void upd(int &x,int y) {x=max(x,y);}
void work()
{
n=read();
for(int i=1;i<=n;i++)
{
b[i].clear();
for(int j=1;j<=n;j++) dp[i][j]=0;
}
for(int i=1;i<=n;i++)
{
a[i]=read();
b[a[i]].push_back(i);
}
for(int l=n;l>=1;l--)
for(int r=l;r<=n;r++)
{
dp[l][r]=dp[l+1][r];
for(auto x:b[a[l]]) if(l<x && x<=r)
upd(dp[l][r],dp[l+1][x-1]+1+dp[x][r]);
}
printf("%d\n",n-1-dp[1][n]);
}
signed main()
{
T=read();
while(T--) work();
}
D. Bridge Club
题目描述
有 \(2^n\) 个人,编号为 \(0\rightarrow 2^n-1\),如果两个人二进制位最多相差 \(1\) 就可以配对,每个人最多配对一次,每对的得分为两个人的点权之和,问最多配 \(k\) 对的最大得分。
\(n\leq 20,k\leq 200\)
解法
可以发现至多 \((2n-1)(k-1)+1\) 条边有用,桶排之后暴力网络流即可。
总结
缩小问题规模(只考虑和答案有关的量),寻找等价类,是解决不仅限于匹配问题的重要方法。
//I was the king under your control~~
#include <cstdio>
#include <vector>
#include <iostream>
#include <queue>
using namespace std;
const int M = 20005;
const int N = 1100005;
const int inf = 0x3f3f3f3f;
#define pii pair<int,int>
#define mp make_pair
int read()
{
int x=0,f=1;char c;
while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
return x*f;
}
int n,m,k,t,cnt,a[N],b[N],c[N];vector<pii> w[N<<1];
int S,T,tot,ans,f[M],in[M],dis[M],flow[M],pre[M],lst[M];
struct edge
{
int v,c,f,next;
}e[N];
void add(int u,int v,int F,int c)
{
e[++tot]=edge{v,c,F,f[u]},f[u]=tot;
e[++tot]=edge{u,-c,0,f[v]},f[v]=tot;
}
int bfs()
{
for(int i=0;i<=T;i++) dis[i]=-inf;
queue<int> q;q.push(S);in[S]=1;
dis[S]=flow[T]=0;flow[S]=inf;
while(!q.empty())
{
int u=q.front();q.pop();in[u]=0;
for(int i=f[u];i;i=e[i].next)
{
int v=e[i].v,c=e[i].c;
if(e[i].f && dis[v]<dis[u]+c)
{
dis[v]=dis[u]+c;
pre[v]=u;lst[v]=i;
flow[v]=min(flow[u],e[i].f);
if(!in[v]) q.push(v),in[v]=1;
}
}
}
return flow[T]>0;
}
signed main()
{
n=read();k=read();
m=1<<n;t=(2*n-1)*(k-1)+1;tot=1;
for(int i=0;i<m;i++)
{
a[i]=read();
b[i]=__builtin_popcount(i);
}
for(int i=0;i<(1<<n);i++) if(b[i]&1)
for(int j=0;j<n;j++)
{
int to=i^(1<<j);
w[a[i]+a[to]].push_back(mp(i,to));
}
for(int i=2000000;i>=0;i--)
{
for(auto x:w[i])
{
int u=x.first,v=x.second;
if(!c[u]) c[u]=++cnt;
if(!c[v]) c[v]=++cnt;
add(c[u],c[v],1,i);
t--;if(t<0) break;
}
if(t<0) break;
}
S=0;T=++cnt;
for(int i=0;i<m;i++) if(c[i])
{
if(b[i]&1) add(S,c[i],1,0);
else add(c[i],T,1,0);
}
while(bfs())
{
if(dis[T]<=0) break;
int zy=T,tmp=min(flow[T],k);
ans+=dis[T]*tmp;k-=tmp;
if(k==0) break;
while(zy!=S)
{
e[lst[zy]].f-=tmp;
e[lst[zy]^1].f+=tmp;
zy=pre[zy];
}
}
printf("%d\n",ans);
}
F. Stations
题目描述
Caught up in confusion . Need a resolution.
有 \(n\) 个塔台排成一排,设第 \(i\) 个塔台的高度是 \(h_i\),覆盖范围是 \(w_i\),\(i\) 能覆盖 \(j\) 的充要条件是:
- \(i\leq j\leq w_i\),\(\forall i<k\leq j,h_k<h_i\)
一开始所有塔台的高度都为 \(0\),覆盖范围都为 \(i\),有下列两种操作:
- \(op=1\),重建操作,把塔台 \(x\) 的高度重建成当前最高,覆盖范围设置成 \(y\)
- \(op=2\),询问操作,设 \(b_i\) 表示覆盖它的塔台数量,求 \(\sum_{l\leq i\leq r} b_i\)
解法
这道题,是我自己做出来的[骄傲.jpg]
由于每次重建都会获得最高的塔台,而且它的有效覆盖范围是 \(y\),我们只需要考虑这个塔台对其他塔台有效覆盖范围的影响即可,不难发现是对 \([1,x)\) 的塔台对 \(x-1\) 取 \(\min\)
既然是取 \(\min\) 操作我们可以考虑势能线段树,所以每个塔台的有效覆盖范围是不难维护的,但是这样难以处理询问,我们不妨再拿一棵线段树维护每个点的被覆盖次数,再更新有效覆盖范围的时候更新它即可。
具体算法:势能线段树在 \(mx>x-1>cx\) 的时候整体取消一个区间的覆盖即可,时间复杂度 \(O(n\log^2 n)\)
#include <cstdio>
#include <iostream>
using namespace std;
const int M = 800005;
#define ll long long
int read()
{
int x=0,f=1;char c;
while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
return x*f;
}
int n,m,mx[M],cx[M],num[M];ll sum[M],tag[M];
//segment tree II : support simple addition
void Down(int i,int l,int r)
{
if(!tag[i]) return ;
int mid=(l+r)>>1,c=tag[i];
sum[i<<1]+=c*(mid-l+1);tag[i<<1]+=c;
sum[i<<1|1]+=c*(r-mid);tag[i<<1|1]+=c;
tag[i]=0;
}
void Add(int i,int l,int r,int L,int R,int c)
{
if(L>r || l>R) return ;
if(L<=l && r<=R)
{
sum[i]+=(r-l+1)*c;tag[i]+=c;
return ;
}
int mid=(l+r)>>1;Down(i,l,r);
Add(i<<1,l,mid,L,R,c);
Add(i<<1|1,mid+1,r,L,R,c);
sum[i]=sum[i<<1]+sum[i<<1|1];
}
ll Ask(int i,int l,int r,int L,int R)
{
if(l>R || L>r) return 0;
if(L<=l && r<=R) return sum[i];
int mid=(l+r)>>1;Down(i,l,r);
return Ask(i<<1,l,mid,L,R)+
Ask(i<<1|1,mid+1,r,L,R);
}
//segment tree I : support interval Min
void down(int i)
{
mx[i<<1]=min(mx[i<<1],mx[i]);
mx[i<<1|1]=min(mx[i<<1|1],mx[i]);
}
void up(int i)
{
num[i]=0;
mx[i]=max(mx[i<<1],mx[i<<1|1]);
cx[i]=max(cx[i<<1],cx[i<<1|1]);
if(mx[i]==mx[i<<1]) num[i]+=num[i<<1];
if(mx[i]==mx[i<<1|1]) num[i]+=num[i<<1|1];
if(mx[i]!=mx[i<<1]) cx[i]=max(cx[i],mx[i<<1]);
if(mx[i]!=mx[i<<1|1]) cx[i]=max(cx[i],mx[i<<1|1]);
}
void ins(int i,int l,int r,int id,int c)
{
if(l==r)
{
Add(1,1,n,id,mx[i],-1);
mx[i]=c;num[i]=1;
Add(1,1,n,id,mx[i],1);
return ;
}
int mid=(l+r)>>1;down(i);
if(mid>=id) ins(i<<1,l,mid,id,c);
else ins(i<<1|1,mid+1,r,id,c);
up(i);
}
void zxy(int i,int l,int r,int c)
{
if(mx[i]<=c) return ;
if(mx[i]>c && c>cx[i])
{
Add(1,1,n,c+1,mx[i],-num[i]);
mx[i]=c;return ;
}
if(l==r)
{
if(c<mx[i]) Add(1,1,n,c+1,mx[i],-1);
mx[i]=c;return ;
}
int mid=(l+r)>>1;down(i);
zxy(i<<1,l,mid,c);
zxy(i<<1|1,mid+1,r,c);
up(i);
}
void upd(int i,int l,int r,int L,int R,int c)
{
if(L>r || l>R) return ;
if(L<=l && r<=R)
{
zxy(i,l,r,c);
return ;
}
int mid=(l+r)>>1;down(i);
upd(i<<1,l,mid,L,R,c);
upd(i<<1|1,mid+1,r,L,R,c);
up(i);
}
signed main()
{
n=read();m=read();
for(int i=1;i<=n;i++)
ins(1,1,n,i,i);
for(int i=1;i<=m;i++)
{
int op=read(),x=read(),y=read();
if(op==1)
{
ins(1,1,n,x,y);
upd(1,1,n,1,x-1,x-1);
}
else
printf("%lld\n",Ask(1,1,n,x,y));
}
}