Codeforces Round #743 (Div. 1)

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\)

\(n,q\leq 2\cdot 10^5\)

解法

这道题,是我自己做出来的[骄傲.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));
	}
}
posted @ 2021-09-28 22:27  C202044zxy  阅读(383)  评论(0编辑  收藏  举报