Codeforces 1131 (div 2)

链接:http://codeforces.com/contest/1131

A Sea Battle#

利用良心出题人给出的图,不难看出答案为\(2*(h1+h2)+2*max(w1,w2)+4\)由于\(w2 \leq w1\),所以答案为\(2*(h1+h2)+2*w1+4\)

#include<cstdio>
int w1,h1,h2,w2,ans;
int main(){
	scanf("%d%d%d%d",&w1,&h1,&w2,&h2);
	ans+=(h1+h2)*2+w1*2+4;
	printf("%d\n",ans);
}

B Draw#

这本质上可以说是一道贪心题吧,我们稍微分类讨论一下
我们假设上一轮的结果为\(x:y\)显然可以分为3种情况,\(x<y,x=y,x>y\)
\(x<y\)\(x>y\)本质一样我们只讨论一种
\(x<y\)对于新一轮的比分\(p:q\)
\(y>p\),那么说明在这几轮比赛中根本不可能出现,继续做就行,
否则,我们可以让前一个人赢,赢球一时爽,一直赢球一直爽,赢到两个人比分一样,然后你拍一,我拍一就好了
\(x=y\)的话,我们发现就是上面的子问题,直接你拍一,我拍一

#include<cstdio>
#include<algorithm>
using namespace std;
int ans=1,la,lb,a,b,n;
int main(){
	scanf("%d",&n);
	for (int i=1;i<=n;i++){
		scanf("%d%d",&a,&b);
		if (a==la&&b==lb)continue;
		if (la>lb){
			if (b<la){
				la=a,lb=b;
				continue;
			}
			ans+=min(a,b)-la+1;
			la=a;lb=b;
		}
		else if (la<lb){
			if (a<lb){
				la=a,lb=b;
				continue;
			}
			ans+=min(a,b)-lb+1;
			la=a;lb=b;
		}
		else {
			ans+=min(a,b)-la;
			la=a,lb=b;
		}
	}
	printf("%d\n",ans);
}

C Birthday#

这题啊,看起来有点难,其实只是纸老抚
数据范围只有100,首先考虑乱搞
发现乱搞凉了,怎么办?只会std::sort的选手突然想到,先排序!
排完序了怎么办呢?我们考虑贪心地将排序后奇数位的数先按顺序填下去,然后将偶数位的数反过来填下去,这样就对了
为什么呢?
我们尝试证明一下这个贪心
我们规定,我们填下去的数是从小到大填的,如果我们现在按这样填不是最优解,那么说明我们存在一种合法方案,使得让两个排完序后相邻的数在环中也相邻,并且这个方案更优
我们设这位为第i位,则i和i+1在环中相邻
然而我们意识到,如果这个方案要更优,则说明i与i+2是原来方案中差距最大的两个数,这样我们调整后才能得到一个更优的方案,一定不会变优
我们开始考虑填入i+2,我们发现,i+2只能和i+1相邻,否则就会和一个小于等于i的数字相邻,那么这个新方案就一定不会比前面那个更优,了不起一样优嘛
我们归纳一下,之后的每个数都存在这个问题,不能与小于等于i的数相邻,这样的环显然是不存在的,所以这个贪心是对的
(不知道我在瞎bb啥,但是应该是对的,考场上我画了个图,现在懒得画了,大家意会一下吧略略略)

#include<cstdio>
#include<algorithm>
int n,a[105],b[105],f[105];
int main(){
	scanf("%d",&n);
	for (int i=1;i<=n;i++)scanf("%d",&a[i]);
	std::sort(a+1,a+n+1);
	b[1]=a[1];
	int mid=(n>>1)+1;
	b[(n>>1)+1]=a[n];f[1]=f[n]=1;
	for (int i=2,j=3;i<mid;i++,j+=2)b[i]=a[j],f[j]=1;
	for (int i=n,j=1;i>mid;j++)if (!f[j])b[i--]=a[j];
	for (int i=1;i<=n;i++)printf("%d ",b[i]);
} 

D Gourmet choice#

乍一看,差分约束!
然后发现不会建图
怎么办
考虑乱搞
我们先考虑假如给出的条件都是大于小于,那么要怎么做
由NOIP2013 普及组 车站分级我们可以得到,拓扑排序!
将所有点按拓扑序编号就行了
如何处理等号?
把用等号连接的点强行压成一个点
当拓扑排序失败或出现自环则输出“No”,否则“Yes”,然后拓扑编号输出

#include<cstdio>
#include<vector>
std::vector<int> v[2050];
struct r{
	int to,last;
}e[4000050];
int f[2050],dep[2050],num=1,mp[2050][2050],head[2050],h=1,q[2050],ans[2050],d[2050],t;
char s[1050][1050];
void add(int u,int v){e[num].to=v;e[num].last=head[u];head[u]=num++;}
int get_fa(int u){return f[u]==u?u:f[u]=get_fa(f[u]);}
int main(){
	int n,m;
	scanf("%d%d",&n,&m);
	for (int i=1;i<=n+m;i++)f[i]=i;
	for (int i=1;i<=n;i++)scanf("%s",s[i]);
	for (int i=1;i<=n;i++)
		for (int j=1;j<=m;j++)if (s[i][j-1]=='='){
			int fu=get_fa(i),fv=get_fa(j+n);
			f[fv]=fu;
		}
	for (int i=1;i<=n+m;i++)v[get_fa(i)].push_back(i);
	for (int i=1;i<=n;i++)
		for (int j=1;j<=m;j++)if (s[i][j-1]=='<'){
			int fu=get_fa(i),fv=get_fa(j+n);
			if (fu==fv){
				printf("No\n");
				return 0;
			}
			if (!mp[fu][fv])add(fu,fv),d[fv]++;
		}
		else if (s[i][j-1]=='>'){
			int fu=get_fa(i),fv=get_fa(j+n);
			if (fu==fv){
				printf("No\n");
				return 0;
			}
			if (!mp[fu][fv])add(fv,fu),d[fu]++;
		}
	for (int i=1;i<=n+m;i++)if (f[i]==i&&!d[i])q[++t]=i,dep[i]=1;
	while (h<=t){
		int u=q[h++];
		for (int i=head[u];i;i=e[i].last){
			d[e[i].to]--;
			if (!d[e[i].to]){
				dep[e[i].to]=dep[u]+1;
				q[++t]=e[i].to;
			}
		}
	}
	for (int i=1;i<=n+m;i++)if (f[i]==i&&!dep[i]){
		printf("No\n");
		return 0;
	}
	printf("Yes\n");
	for (int i=1;i<=n+m;i++)
		for (int j=v[i].size()-1;j>=0;j--)ans[v[i][j]]=dep[i];
	for (int i=1;i<=n+m;i++){
		printf("%d ",ans[i]);
		if (i==n)putchar('\n');
	}
}

E String Multiplication#

由于时间问题,蒟蒻博主并没有时间看完这道题的题面(由于当场F题过的人多所以先跳题了)
考后发现,这题并不难
我们读完题目可以发现,原来的串相邻的位置都被隔开了!利用这个就可以解决这题了
我们可以发现,对于新串,对我们有用的信息只有最左有几个相同字母,最右有几个相同字母以及每种字母最多有多少个连续的
于是我们可以分字母统计这个问题
若新串首尾不同,则首字母的连续长度更新为max{新串最左的连续长度+1,串中该字母的最长连续长度}(最后一个字母也是一样的)
如果相同(该串不止含有一种字母),则首尾字母的最长连续长度为max{新串最左的连续长度+1+最右连续长度,串中该字母的最长连续长度}
若该串中只有一种字母,则这种字母连续长度更新为max{原串中连续长度*(新串长度+1),1}
当然只要出现过的字母连续长度最少都为1,出现过的字母记得和1取max就好了
按字母更新完后,在这些字母中找一个连续长度最大的输出长度就好了

#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
int n,cnt[35],p[35],ans;
char s[100050];
int main(){
	scanf("%d",&n);
	for (int i=1;i<=n;i++){
		scanf("%s",s);
		int len=strlen(s),tmp=1,ft,flag=0;
		for (int j=0;j<26;j++)p[j]=(cnt[j]!=0);
		for (int j=1;j<len;j++)if (s[j]==s[j-1])tmp++;
		else{
			if (!flag)ft=tmp,flag=1;
			p[s[j-1]-'a']=max(p[s[j-1]-'a'],tmp);
			tmp=1;
		}
		p[s[len-1]-'a']=max(p[s[len-1]-'a'],tmp);
		if (!flag)p[s[0]-'a']=max(tmp+cnt[s[0]-'a']*tmp+cnt[s[0]-'a'],p[s[0]-'a']);
		else if (s[0]==s[len-1])p[s[0]-'a']=max(cnt[s[0]-'a']?tmp+ft+1:0,p[s[0]-'a']);
		else p[s[0]-'a']=max((cnt[s[0]-'a']!=0)+ft,p[s[0]-'a']),p[s[len-1]-'a']=max((cnt[s[len-1]-'a']!=0)+tmp,p[s[len-1]-'a']);
		for (int j=0;j<26;j++)cnt[j]=p[j];
	}
	for (int i=0;i<26;i++)ans=max(cnt[i],ans);
	printf("%d\n",ans);
} 

F Asya And Kittens#

蒟蒻博主因为数组开小,差点没过!心态爆炸
这题呢,用心感受一下题目,每次把两个东西合并起来,是不是很像并查集
没错,这题就是用的并查集
首先我们把每个点初始化,合并的时候考虑新建一个点\(tmp\),把这次合并的两个点\(fu,fv\)都连到这个新点\(tmp\)上,并且\(f[tmp]=f[fu]=f[fv]=tmp\)
然后\(add\_edge(tmp,fu),add\_edge(tmp,fv)\)
我们做完之后得到了什么呢,每次操作都增加了一层,一个点向两个点连边,这不就是棵树吗
我们用\(dfs\)遍历这棵树,每次遇到\(id \leq n\)的就加入答案就行了,由于这棵树形态非常优美,所以做出来就是对的

#include<cstdio>
struct r{
	int to,last;
}e[300050];
int num=1,head[300050],ans[150050],cnt,tmp,n,f[300050],x,y;
void add(int u,int v){
	e[num].to=v;e[num].last=head[u];head[u]=num++;
}
int get_fa(int u){return f[u]==u?u:f[u]=get_fa(f[u]);}
void dfs(int u){
	if (u<=n)ans[++cnt]=u;
	for (int i=head[u];i;i=e[i].last)dfs(e[i].to);
}
int main(){
	scanf("%d",&n);
	tmp=n;
	for (int i=1;i<=n;i++)f[i]=i;
	for (int i=1;i<n;i++){
		scanf("%d%d",&x,&y);
		int fu=get_fa(x),fv=get_fa(y);
		if (fu!=fv){
			tmp++;
			add(tmp,fv),add(tmp,fu);
			f[tmp]=f[fu]=f[fv]=tmp;
		}
	}
	dfs(tmp);
	for (int i=1;i<=n;i++)printf("%d ",ans[i]);
}

G Most Dangerous Shark#

看了好几次题解都没看懂,我真是太弱啦
最后买了一包山楂片,边吃,终于看懂了题解(这绝对不是帮山楂片打广告,正经博主怎么可能打广告呢)
第一次做手动栈大题,题好神啊
首先我们可以写出最原始的dp方程\(dp[i]=min(dp[l[i]-1]+c[i],dp[j-1]+c[j](j<i\leq r[j]))\)
其中\(l[i]\)表示将第\(i\)个骨牌向左推倒能推到的最左的骨牌,\(r[i]\)当然就是向右推到最右的啦
我们现在已经可以得到一个线段树解法了,但是本题需要我们在\(O(n)\)的复杂度内解决这个问题
我们可以尝试使用栈,我们先倒过来做,将元素压栈,若遇到\(i\leq s[top]+h[s[top]]\)那么我们就弹栈并且将弹出的元素的\(l[i]\)设为\(i+1\)
然后我们就得到了\(l[i]\)
而对于\(r[i]\)的话,其实我们没必要把它求出来,只要边做边dp,然后维护一个栈中前缀最小值就好啦

#include<cstdio>
#include<vector>
#include<algorithm>
#define ll long long 
std::vector <int> v1[250050],v2[250050];
int k[250050],n,m,l[10000050],t,h[10000050],v,tmp,s[10000050];
ll mn[10000050],c[10000050],dp[10000050];
int main(){
	scanf("%d%d",&n,&m);
	for (int i=1;i<=n;i++){
		scanf("%d",&k[i]);
		for (int j=k[i];j;j--)scanf("%d",&v),v1[i].push_back(v);
		for (int j=k[i];j;j--)scanf("%d",&v),v2[i].push_back(v); 
	}
	int q,id,mul;
	scanf("%d",&q);
	for (int i=1;i<=q;i++){
		scanf("%d%d",&id,&mul);
		for (int j=0;j<k[id];j++){
			++tmp;h[tmp]=v1[id][j];
			c[tmp]=v2[id][j]*1ll*mul;
		}
	}
	for (int i=m;i>=1;i--){
		while (t&&i<=s[t]-h[s[t]])l[s[t--]]=i+1;
		s[++t]=i;
	}
	while (t)l[s[t--]]=1;mn[0]=1e18;
	for (int i=1;i<=m;i++){
		while (t&&i>=s[t]+h[s[t]])t--;
		dp[i]=std::min(dp[l[i]-1]+c[i],mn[t]);
		s[++t]=i;
		mn[t]=std::min(mn[t-1],dp[i-1]+c[i]);
	}
	printf("%lld\n",dp[m]);
}
posted @ 2019-02-27 10:03  Warm_Angel  阅读(407)  评论(6编辑  收藏  举报