题目来源:
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-....
N−1−S1−S2−S3−....。每个连通分量所对应的不连通的节点对数等于
S
i
∗
(
N
−
S
i
)
S_i*(N-S_i)
Si∗(N−Si) 。 则删除节点后不连通的节点对数为
s
u
m
=
∑
(
S
i
∗
(
N
−
S
i
)
)
/
2
sum=∑(S_i*(N-S_i))/2
sum=∑(Si∗(N−Si))/2
对于问题2,当我们选出关键节点后,只要求出删除关键节点后最大的两个连通分量
S
m
a
x
1
和
S
m
a
x
2
S_{max1} 和 S_{max2}
Smax1和Smax2,答案即为
s
u
m
−
S
m
a
x
1
−
S
m
a
x
2
sum-S_{max1}-S_{max2}
sum−Smax1−Smax2
#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;
}