凉脾的比赛 题目解析

题目来源:
2017 ICPC East-Central NA Regional Contest

我挑了里面解出人数最多的六道题

DRM Messages

题意:
模拟DRM加密的三个步骤:
第一步:划分。把字符串从中间分成两段。
第二步:旋转。算出左字串每个字符相对于A的偏移值之和,然后每个字符加长这个和值。再对右字串做同样处理。
第三步:合并。算出处理后的右字串每个字符相对于A的偏移值,每个右字串的字符所对应的左字串的字符都加上对应的偏移值。

解法:
模拟即可

#include <bits/stdc++.h>
using namespace std;
string s;
int main(){
    cin>>s;
    int len=s.length();
    int halflen=len/2;
    int lsum=0,rsum=0;
    for(int i=0;i<len;i++) s[i]-='A';
    for(int i=0;i<halflen;i++) lsum+=s[i];
    for(int i=halflen;i<len;i++) rsum+=s[i];
    for(int i=0;i<halflen;i++) putchar('A'+(s[i]+lsum+s[i+halflen]+rsum)%26);
    return 0;
}

Game of Throwns

题意:
总共有n个人,k条命令。人从0开始编号,开始的时候0号拿一个蛋蛋。
命令分两种:

  • 一个数字t,表示把蛋蛋顺时针传t次,t可能小于0
  • 一个“undo”接一个数字t,表示抵消最后t条命令。抵消的时候跳过其他undo。

例子:

输入:
5 10 
7 -3 undo 1 4 3 -9 5 undo 2 undo 1 6  

运行过程:
+7
+7-3
+7
+7+4
+7+4+3
+7+4+3-9
+7+4+3-9+5
+7+4+3
+7+4
+7+4+6
最终偏移值为(+7+4+6)%5=2

解法:
用队列或数组存储命令列表,然后模拟。
注意了,这题是我找来教大家负数取模的,具体做法看代码

总结:
当要求a模b,最终结果取自然数时,需分情况讨论:

  • 当a>=0 : a%b
  • 当-b<=a<0 : (a+b)%b
  • 当a<-b : (a%b+b)%b
#include <bits/stdc++.h>
using namespace std;
char s[20];
int cmd[105];
int main(){
	int n,k,cur;
	scanf("%d%d",&n,&k);
	cur=0;
	for(int i=0;i<k;i++){
		scanf("%s",s);
		if(isdigit(s[0])||s[0]=='-') cmd[cur++]=stoi(string(s));
		else{
			int undo;
			scanf("%d",&undo);
			cur=max(cur-undo,0);
		}
	}
	int ans=0;
	for(int i=0;i<cur;i++) ans=(ans+cmd[i]%n+n)%n;
    /*
    易错点:负数取模
    cmd[i]本身可能小于-ans-n,加ans后会小于-n,故先对它取模
    答案要求结果为0~n-1,但(ans+cmd[i]%n)%n可能小于0,故要先加上n再%n
    */
	printf("%d",ans);
	return 0;
}

Sheba’s Amoebas

题意:
给定一个黑白矩阵,规定每个格子与上下左右和斜对角的格子相互连通。求里面不相连通的黑块儿有几个。

解法:
DFS每个格子。单次DFS到的格子编上相同的编号,每次DFS完后若本次访问的黑格子数>0,就把总编号数+1。

#include <bits/stdc++.h>
using namespace std;
int m,n;
char s[105][105];
char id[105][105];
int nowid;
int cnt;
void dfs(int y,int x){
	if(x<0||y<0||x>=n||y>=m||s[y][x]!='#'||id[y][x]>0) return;
	id[y][x]=nowid;
	cnt++;
	for(int oy=-1;oy<=1;oy++)
		for(int ox=-1;ox<=1;ox++)
			dfs(y+oy,x+ox);
}
int main(){
	scanf("%d%d",&m,&n);
	for(int y=0;y<m;y++){
		scanf("%s",s[y]);
		memset(id[y],0,sizeof(int)*n);
	}
	nowid=1;
	for(int y=0;y<m;y++)
		for(int x=0;x<n;x++){
			cnt=0;
			dfs(y,x);
			if(cnt>0) nowid++;
		}
	printf("%d",nowid-1);
	return 0;
}

Keeping On Track

题意:
给定一棵n条边的树(n<=10000)。存在一个关键节点,当删除该节点时,不连通的节点对的数量最多。
问题1:求出删除关键节点后不连通的节点对的数量
问题2:在删除关键节点后添加一条最优边,使得恢复连通的节点对的数量最多,求出加上该最优边后不连通的节点对的数量。

分析:
任取一个节点作为根节点,把无根树变成有根树。

此时再在这棵树上随便取一个节点进行分析。
取出来的节点应该有一条父分支和零个或多个子分支。特别的,根节点没有父分支。我们把这些分支给编号。
当把这个节点破坏掉时,每条分支都会变成一个连通分量(相互连通的一大块儿节点称之为一个连通分量)。设总结点数为 N N N。此时连通分量的节点数就是对应子树的节点数,记为 S i S_i Si,可以通过对这个分支跑一遍DFS求得。而父分支所对应节点数则等于 N − 1 − S 1 − S 2 − S 3 − . . . . N-1-S_1-S_2-S_3-.... N1S1S2S3....。每个连通分量所对应的不连通的节点对数等于 S i ∗ ( N − S i ) S_i*(N-S_i) Si(NSi) 。 则删除节点后不连通的节点对数为 s u m = ∑ ( S i ∗ ( N − S i ) ) / 2 sum=∑(S_i*(N-S_i))/2 sum=(Si(NSi))/2
对于问题2,当我们选出关键节点后,只要求出删除关键节点后最大的两个连通分量 S m a x 1 和 S m a x 2 S_{max1} 和 S_{max2} Smax1Smax2,答案即为 s u m − S m a x 1 − S m a x 2 sum-S_{max1}-S_{max2} sumSmax1Smax2

#include <bits/stdc++.h>
using namespace std;
const int MAXN=10005;
int nodecnt;
vector<int> g[MAXN];
bool vis[MAXN];
vector<int> component[MAXN];//每个节点对应的连通分量
int dfs(int u){
	vis[u]=true;
	for(int i=0;i<g[u].size();i++)
		if(!vis[g[u][i]]) 
			component[u].push_back(dfs(g[u][i]));//连通分量的节点数等于子树的节点数
	int ret=0;
	for(int i=0;i<component[u].size();i++) ret+=component[u][i];
	if(nodecnt-1-ret>0) component[u].push_back(nodecnt-1-ret);
    //前辈节点数大于0时,前辈节点对应连通数等于总结点数-1-子节点数
	ret++;//返回值要加上当前节点,故加1
	return ret;
}
int main(){
	int m;
	scanf("%d",&m);
	nodecnt=m+1;//定理:树的总节点数=总边数+1
	while(m--){
		int u,v;
		scanf("%d%d",&u,&v);
		g[u].push_back(v);
		g[v].push_back(u);
	}
	dfs(0);
	int maxsum=-1;
	int maxid=0;
	for(int i=0;i<nodecnt;i++){
        //破坏节点i后产生的不连通节点对数
		int sum=0;
		for(int j=0;j<component[i].size();j++) sum+=component[i][j]*(nodecnt-1-component[i][j]);
		if(sum>=maxsum){
			maxsum=sum;
			maxid=i;
		}
	}
	maxsum/=2;

    //排序,跳出最大的两个
	sort(component[maxid].begin(),component[maxid].end(),greater<int>());
	int ans=maxsum-component[maxid][0]*component[maxid][1];
	printf("%d %d",maxsum,ans);
	return 0;
}

A Question of Ingestion

题意:
有最多100天。每天有一个食物量,你一开始有一个最大胃口表示你最开始能吃多少食物。
如果你昨天吃了,那么今天的胃口为昨天的2/3。如果你前天吃了,昨天没吃,那么你的胃口可以恢复到前天的情况。
如果你有连续两天没吃了,那么你就可以恢复到最大胃口了。问怎样安排使一共吃到的食物最多?

解法:
赤裸裸的DP
先根据题意,每x天的饭量取值只能为m*[(2/3) ^ x],x取值为0,1,2…n,所以我们可以先把每天饭量的可能取值求出来,存进数组e里。
然后定义dp[i][j]表示第i天,饭量为e[j]时,吃掉的食物总量。
先把初始值设为-1,表示未赋值。然后让dp[i][0]全等于0,表示一直不吃的情况。
然后分类讨论:
今天吃完后的分值为dp[i][j]+min(e[j],a),记为aftereat

  • 今天吃,明天也吃:dp[i+1][j+1]=max(dp[i+1][j+1],aftereat)
  • 今天吃,明天不吃,后天吃:dp[i+2][j]=max(dp[i+2][j],aftereat)
  • 今天吃,明天不吃,后天也不吃,大后天吃:dp[i+3][0]=max(dp[i+3][0],aftereat)
#include <bits/stdc++.h>
using namespace std;
int dp[105][105];
int e[105];
int main(){
	int n,m,a;
	scanf("%d%d",&n,&m);
	memset(dp,-1,sizeof(dp));
	for(int i=0;i<n;i++) dp[i][0]=0;
	e[0]=m;
	for(int i=1;i<=n;i++) e[i]=e[i-1]*2/3;
	for(int i=0;i<n;i++){
		scanf("%d",&a);
		for(int j=0;j<=n;j++){
			if(dp[i][j]<0) continue;
			int aftereat=dp[i][j]+min(e[j],a);
			dp[i+1][j+1]=max(dp[i+1][j+1],aftereat);
			dp[i+2][j]=max(dp[i+2][j],aftereat);
			dp[i+3][0]=max(dp[i+3][0],aftereat);
		}
	}
	int ans=0;
	for(int j=0;j<=n;j++) ans=max(ans,dp[n][j]);
	printf("%d",ans);
	return 0;
}

Is-A? Has-A? Who Knowz-A?

题意:
有n个关系,有m个询问,关系有a is b,a has b。注意(a is b!= b is a),然后 is和has都具有传递性。
每次询问问你a,b的关系是否正确。
如果a到b有一条只由is组成的路径,那就称a is b。
如果a到b的路径上至少含一个has,那就称a has b。

解法:
建图,is关系一个图,has关系一个图。
10000个节点询问200次,所以可以直接BFS或DFS。
对于is询问,在is图上跑一遍BFS看看能不能从a到b即可。
对于has询问,在is图和has图的混合图上跑,跑的时候每个访问器要携带一个属性信息,初始设置为is型。记录访问状态的vis数组也要分成 not_vis,is和has三种取值。
因为图的交叉所以需要处理四种情况:
is访问器访问has边:把is访问器改成has访问器
is访问器访问has节点:终止访问
has访问器访问is边:无影响
has访问器访问is节点:把is节点改成has边
最后看看有没有has信息访问到目标节点即可。

#include <bits/stdc++.h>
using namespace std;
const int MAXN=500+5;

map<string,int> mp;

vector<int> is[MAXN];
vector<int> has[MAXN];
int iscnt[MAXN];
int hascnt[MAXN];

int vis[MAXN];
const int NOT_VIS=0;
const int IS=1;
const int HAS=2;

struct node{int id,type;node(int _id,int _type){id=_id;type=_type;}};
void bfsis(int s,int t){
	queue<int> q;
	memset(vis,0,sizeof(vis));
	q.push(s);
	vis[s]=IS;
	while(!q.empty()){
		int u=q.front();
		q.pop();
		if(u==t) return;
		for(int i=0;i<iscnt[u];i++){
			int v=is[u][i];
			if(vis[v]==NOT_VIS){
				vis[v]=IS;
				q.push(v);	
			}
		}
	}
	return;
}
void bfshas(int s,int t){
	queue<node> q;
	memset(vis,0,sizeof(vis));
	q.push(node(s,IS));
	vis[s]=IS;
	while(!q.empty()){
		int u=q.front().id;
		int utype=q.front().type;
		q.pop();
		if(u==t&&vis[u]==HAS) return;
		for(int i=0;i<hascnt[u];i++){
			int v=has[u][i];
			if(vis[v]!=HAS){
				vis[v]=HAS;
				q.push(node(v,HAS));
			}
		}
		for(int i=0;i<iscnt[u];i++){
			int v=is[u][i];
			if(utype==HAS&&vis[v]!=HAS){
				vis[v]=HAS;
				q.push(node(v,HAS));
			}
			else if(utype==IS&&vis[v]==NOT_VIS){
				vis[v]=IS;
				q.push(node(v,IS));
			}
		}
	}
	return;
}
int main(){
	int n,m;
	int nodecnt=0;
	memset(iscnt,0,sizeof(iscnt));
	memset(hascnt,0,sizeof(hascnt));
	cin>>n>>m;
	while(n--){
		string s,t,type;
		cin>>s>>type>>t;
		if(mp.count(s)==0) mp[s]=nodecnt++;
		if(mp.count(t)==0) mp[t]=nodecnt++;
		int u=mp[s];
		int v=mp[t];
		if(type[0]=='i'){
			iscnt[u]++;
			is[u].push_back(v);
		}
		else{
			hascnt[u]++;
			has[u].push_back(v);
		}
	}
	for(int T=1;T<=m;T++){
		string s,t,type;
		cout<<"Query "<<T<<": ";
		cin>>s>>type>>t;
		int u=mp[s];
		int v=mp[t];
		if(type[0]=='i'){
			bfsis(u,v);
			if(vis[v]==IS) cout<<"true\n";
			else cout<<"false\n";
		}
		else{
			bfshas(u,v);
			if(vis[v]==HAS) cout<<"true\n";
			else cout<<"false\n";
		}
	}
	return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值