CodeForces CF #517 Div.2

A. Golden Plate
水题,公式都不用推,循环就完事了。
http://codeforces.com/contest/1072/problem/A

int n,m,k;
	cin>>n>>m>>k;
	int ans=0;
	while(k--){
		ans+=2*n+2*m-4;
		n-=4;
		m-=4;
	}
	cout<<ans<<endl;

B. Curiosity Has No Limits
题意是给你两个生成函数和两个生成出来的数列,让你去找原数列(也可能不存在)。我的做法是,对于任意的i,ai和bi总共只有16种不同的可能,穷举他们,去找ti和ti+1可能的存在形式。发现除了一组ai和bi可以得到两组ti和ti+1外,其他的至多只能推出一种。那么我们穷举可能t1和t2,可以在O(n)时间内根据上式推出后面的元素。如果每一种都推不出,那么说明无解。
http://codeforces.com/contest/1072/problem/B

const int maxn=2e5+7;
struct node{
	int x,y;
}c[maxn];
int a[maxn],b[maxn]; 
int n;
int err=0;
int ans[maxn];

void check(){
	if(err==-1)return;
	for(int i=2;i<n;++i){
		if(c[i].x==4)ans[i+1]=(~ans[i])&3;
		else if(ans[i]==c[i].x)ans[i+1]=c[i].y;
		else if(ans[i]==c[i].y)ans[i+1]=c[i].x;
		else return;
	}
	err=-1;
	return;
}

int main()
{
	scanf("%d",&n);
	for(int i=1;i<n;++i)scanf("%d",&a[i]);
	for(int i=1;i<n;++i)scanf("%d",&b[i]);
	for(int i=1;i<n;++i){
		if(a[i]==b[i])c[i].x=c[i].y=a[i];
		else if(a[i]==1&&b[i]==0)c[i].x=1,c[i].y=0;
		else if(a[i]==2&&b[i]==0)c[i].x=2,c[i].y=0;
		else if(b[i]==1&&a[i]==3)c[i].x=1,c[i].y=3;
		else if(b[i]==2&&a[i]==3)c[i].x=2,c[i].y=3;
		else if(a[i]==3&&b[i]==0)c[i].x=c[i].y=4;
		else err=1;
	}
	if(!err){
		if(c[1].x==4){
			if(err!=-1)ans[1]=1,ans[2]=2,(check());
			if(err!=-1)ans[1]=2,ans[2]=1,(check());
			if(err!=-1)ans[1]=0,ans[2]=3,(check());
			if(err!=-1)ans[1]=3,ans[2]=0,(check());
		}
		else{
			if(err!=-1)ans[1]=c[1].x,ans[2]=c[1].y,(check());
			if(err!=-1)ans[1]=c[1].y,ans[2]=c[1].x,(check());
		}
	}
	
	if(err==-1){
		cout<<"YES"<<endl;
		for(int i=1;i<=n;i++)cout<<ans[i]<<" ";
	}
	else cout<<"NO"<<endl;

PS:不过这题我写的麻烦了,因为显然对于每一个ai(或者bi)我们可以把它拆成两位,每一位对应t中间相对的位,这样拆位之后处理就可以更加简便,因为变量更少。当然对复杂度不大。但是这样可以处理更大规模的问题,如果ai的范围是0-255,那么如果不拆位的话难度就过于大了。所以拆位在处理位运算的时候是一种非常重要的思想。

C. Cram Time
贪心题。显然我们不难发现,答案ans的上界满足如下性质:
①(ans+1)*ans/2<=(a+b)
那么接下来我们希望证明该上界就是我们需要的答案。
我一开始想的是dp,但是稍加思索之后发现,这个上界是一定能够取得的,以下给出证明:
1.若MIN(a,b)<=ans,那么我们对把第MIN(a,b)天安排在a和b中较小的那一天,由公式①可知剩下的时间一定可以安排在b里面。
2.若MIN(a,b)>ans,那么把第ans天安排在较小的那一天,就构成了一个ans-1的子问题,重复本步骤即可,最后必定会在步骤1中得到解。

LL n,m,sum,maxn;
LL ans1[100005],ans2[100005],cnt1=0,cnt2=0,sum1=0,sum2=0;

int main()
{
	cin>>n>>m;
	sum=m+n;
	LL l=0,r=n+m+1;
	while(l<r-1){
		LL mid=(l+r)>>1;
		if((mid+1)*mid<=(n+m)*2)l=mid;
		else r=mid;
	}
	maxn=l;
	for(int i=maxn;i;--i){
		if(sum1+i<=n){
			sum1+=i;
			ans1[++cnt1]=i;
		}
		else{
			sum2+=i;
			ans2[++cnt2]=i;
		}
	}
	cout<<cnt1<<endl;
	for(int i=1;i<=cnt1;++i)cout<<ans1[i]<<" ";
	cout<<endl;
	cout<<cnt2<<endl;
	for(int i=1;i<=cnt2;++i)cout<<ans2[i]<<" ";
	cout<<endl;
	return 0;
}

D. Minimum path
不难看出,这是很裸的一道dp题。
要这么说其实也对,至少第一个代码段是dp。问题的第一部分还是比较简单,用dp保存还能将多少个非a变成a,然后考虑当前格子是否是a,然后将变掉的字母换成a就可以了。
接下来我们就要找字典序最小的串了,那么很容易想到我们熟悉的dp:

dp[i][j]=min(dp[i-1][j],dp[i][j-1])+c[i][j]
其中dp保存到第i行第j列的字典序最小字符串,c保存图中第i行第j列的字母。

如果这样粗暴的存,那么是一个O(N^3)的空间复杂度,给定的n=2000,大概率会爆空间,所以我把它压缩成了一个一维的dp:

dp[j]=max(dp[j-1],dp[j])+c[i][j]
考虑第i行第j列的状态,那么他或者从左边来,或者从上边来。在没有访问过本格的情况下,dp[j]里面存的其实就是上一上本列的状态,而dp[j-1]则因为已经被更新过,所以存储的是本行j-1列的状态。

然后算算时间复杂度O(N^3),考虑到我的常数非常小,我认为应该可以通过,果不其然,一发WA。

然后我学习到了一种更加巧妙的dp方法,虽然dp本身仍然是O(N^2),但是免去了比较string的时间消耗。我们用一个(bool)used来表示某个点是否可能是最优解中的点,显然,出发点必定在其中。接下来,我们第一重循环穷举k=i+j(0<k<=2*n-2),然后选取其中连接上一层中used中的点中最小的点标记为used,显然至少能够找到一个。由循环不变式可以证明,在当前层中,从选入used中的点回溯必定是最优解:

初始化:当k==0时,只有一个元素,显然成立
保持:当在第k-1层满足该性质时,对于每有被选中的数字,无非包括两种情况。一种是未被上层used连接的(无论大小),那么由于字典序的性质,如果他被选上,那么之前一定不是最优的,所以舍去。另一种是被上一层连接但不是最小的,那么当然也不是最优的,所以舍去。剩下的满足条件,且因为上一层至少有一个在used中的,所以本层也至少能找到一个放进used中。
终止:在到达最后一层时停止。

显然每一层放入used的字母是唯一值,我们保存他们加入used的顺序,就可以得到字典序最小的答案。

char c[2005][2005];
int lt[2005][2005];
bool used[2005][2005];
int n;
int now=1;
int maxx=0;

int getlastlt(int i,int j){
	if(!i)return lt[i][j-1];
	if(!j)return lt[i-1][j];
	return MAX(lt[i][j-1],lt[i-1][j]);
}

string MINn(string x,string y){
	int la=x.size(),lb=y.size();
	int p=0;
	while(p<la&&p<lb){
		if(x[p]<y[p])return x;
		if(x[p]>y[p])return y;
		p++;
	}
	return x;
}

int main()
{
	INI(lt);
	INI(used);
	cin>>n>>lt[0][0];
	for(int i=0;i<n;++i)scanf("%s",c[i]);
	if(c[0][0]!='a'&&lt[0][0])--lt[0][0],c[0][0]='a';
	for(int i=0;i<n;++i){
		for(int j=0;j<n;++j){
			if(i||j){
				int tmp=getlastlt(i,j);
				if(c[i][j]=='a')tmp++,maxx=i+j;
				if(tmp)lt[i][j]=--tmp,c[i][j]='a';
			}
		}
	}//edit the map
	
	string ans;
	ans.push_back(c[0][0]);
	used[0][0]=1;
	for(int i=1;i<=2*n-2;++i){
		int rr,cc;
		char minn='z'+1;
		if(i<=n-1){
			rr=i;
			cc=0;
			while(rr>=0){
				if((rr&&used[rr-1][cc])||(cc&&used[rr][cc-1]))minn=MIN(minn,c[rr][cc]),used[rr][cc]=1;	
				++cc;
				--rr;
			}
			rr=i;
			cc=0;
			while(rr>=0){
				if(c[rr][cc]!=minn)used[rr][cc]=0;
				++cc;
				--rr;
			}
		}
		else{
			rr=n-1;
			cc=i-rr;
			while(cc<n){
				if((rr&&used[rr-1][cc])||(cc&&used[rr][cc-1]))minn=MIN(minn,c[rr][cc]),used[rr][cc]=1;
				--rr;
				++cc;
			}
			rr=n-1;
			cc=i-rr;
			while(rr>=0){
				if(c[rr][cc]!=minn)used[rr][cc]=0;
				++cc;
				--rr;
			}
		}
		ans.push_back(minn);
	}
	cout<<ans<<endl;
	return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_42778110/article/details/83374728
今日推荐