noip2009提高组解题报告
NOIP2009潜伏者
题目描述
R 国和S 国正陷入战火之中,双方都互派间谍,潜入对方内部,伺机行动。
历尽艰险后,潜伏于 S 国的R 国间谍小C 终于摸清了S 国军用密码的编码规则:
1. S 国军方内部欲发送的原信息经过加密后在网络上发送,原信息的内容与加密后所得的内容均由大写字母‘A’-‘Z’构成(无空格等其他字符)。
2. S 国对于每个字母规定了对应的“密字”。加密的过程就是将原信息中的所有字母替换为其对应的“密字”。
3. 每个字母只对应一个唯一的“密字”,不同的字母对应不同的“密字”。“密字”可以和原字母相同。
例如,若规定‘A’的密字为‘A’,‘B’的密字为‘C’(其他字母及密字略),则原信息“ABA”被加密为ACA”。
现在,小 C 通过内线掌握了S 国网络上发送的一条加密信息及其对应的原信息。小C希望能通过这条信息,破译S 国的军用密码。小C 的破译过程是这样的:扫描原信息,对于原信息中的字母x(代表任一大写字母),找到其在加密信息中的对应大写字母y,并认为在密码里y 是x 的密字。如此进行下去直到停止于如下的某个状态:
1. 所有信息扫描完毕,‘A’-‘Z’ 所有 26 个字母在原信息中均出现过并获得了相应的“密字”。
2. 所有信息扫描完毕,但发现存在某个(或某些)字母在原信息中没有出现。
3. 扫描中发现掌握的信息里有明显的自相矛盾或错误(违反S 国密码的编码规则)。例如某条信息“XYZ”被翻译为“ABA”就违反了“不同字母对应不同密字”的规则。
在小 C 忙得头昏脑涨之际,R 国司令部又发来电报,要求他翻译另外一条从S 国刚刚截取到的加密信息。现在请你帮助小C:通过内线掌握的信息,尝试破译密码。然后利用破译的密码,翻译电报中的加密信息。
【输入输出样例 1】
AA
AB
EOWIE
【输入输出样例 2】
QWERTYUIOPLKJHGFDSAZXCVBN
ABCDEFGHIJKLMNOPQRSTUVWXY
DSLIEWO
【输入输出样例 3】
MSRTZCJKPFLQYVAWBINXUEDGHOOILSMIJFRCOPPQCEUNYDUMPP
YIZSDWAHLNOVFUCERKJXQMGTBPPKOIYKANZWPLLVWMQJFGQYLL
FLSO
【输入输出样例 1】
Failed
【输入输出样例 2】
Failed
【输入输出样例 3】
NOIP
输入
共3 行,每行为一个长度在1 到100 之间的字符串。
第 1 行为小C 掌握的一条加密信息。
第 2 行为第1 行的加密信息所对应的原信息。
第 3 行为R 国司令部要求小C 翻译的加密信息。
输入数据保证所有字符串仅由大写字母‘A’-‘Z’构成,且第1 行长度与第2 行相等。
输出
共1 行。
若破译密码停止时出现 2,3 两种情况,请你输出“Failed”(不含引号,注意首字母大
写,其它小写)。
否则请输出利用密码翻译电报中加密信息后得到的原信息。
/* 字符串的处理 模拟 开两个bool数组标记26个字母是否都匹配过了,只要有一个没有匹配,就输出failed。 匹配要保证唯一性 密文对应的原文不同或者原文对应的密文不同都不符合 第一次写把匹配的for循环中i赋成了1~len3,调了很久都没发现(蠢哭) */ #include<iostream> #include<cstring> using namespace std; int a1[110],a2[110],a3[110]; int len1,len3; bool b1[27]={0},b2[27]={0}; int r1[27],r2[27]; char ch(int x) { return 'A'+x-1; } int in(char x) { return x-'A'+1; } int main() { string s1,s2,s3; cin>>s1>>s2>>s3; len1=s1.length(); len3=s3.length(); for(int i=0;i<len1;i++) { a1[i+1]=in(s1[i]); a2[i+1]=in(s2[i]); } for(int i=0;i<len3;i++) a3[i+1]=in(s3[i]); for(int i=1;i<=len1;i++) { if(r1[a1[i]]!=a2[i] && r1[a1[i]]!=0 || r2[a2[i]]!=a1[i] && r2[a2[i]]!=0) //匹配矛盾 { cout<<"Failed"<<endl; return 0; } r1[a1[i]]=a2[i];r2[a2[i]]=a1[i]; b1[a1[i]]=1;b2[a2[i]]=1; } for(int i=1;i<=26;i++) if(!b1[i]||!b2[i]) { cout<<"Failed"<<endl; return 0; } for(int i=1;i<=len3;i++) cout<<ch(r1[a3[i]]); cout<<endl; return 0; }
[NOIP09]Hankson的趣味题
题目描述
Hanks 博士是BT (Bio-Tech,生物技术) 领域的知名专家,他的儿子名叫Hankson。现在,刚刚放学回家的Hankson 正在思考一个有趣的问题。
今天在课堂上,老师讲解了如何求两个正整数c1 和c2 的最大公约数和最小公倍数。现在Hankson 认为自己已经熟练地掌握了这些知识,他开始思考一个“求公约数”和“求公倍数”之类问题的“逆问题”,这个问题是这样的:已知正整数a0,a1,b0,b1,设某未知正整数x 满足:
1. x 和a0 的最大公约数是a1;
2. x 和b0 的最小公倍数是b1。
Hankson 的“逆问题”就是求出满足条件的正整数x。但稍加思索之后,他发现这样的x 并不唯一,甚至可能不存在。因此他转而开始考虑如何求解满足条件的x 的个数。请你帮助他编程求解这个问题。
输入格式
第一行为一个正整数n,表示有n 组输入数据。接下来的n 行每行一组输入数据,为四个正整数a0,a1,b0,b1,每两个整数之间用一个空格隔开。输入数据保证a0 能被a1 整除,b1 能被b0 整除。
【数据范围】
对于 50%的数据,保证有1≤a0,a1,b0,b1≤10000 且n≤100。
对于 100%的数据,保证有1≤a0,a1,b0,b1≤2,000,000,000 且n≤2000。
输出格式
每组输入数据的输出结果占一行,为一个整数。
对于每组数据:若不存在这样的 x,请输出0;
【说明】
第一组输入数据,x 可以是9、18、36、72、144、288,共有6 个。
第二组输入数据,x 可以是48、1776,共有2 个。
若存在这样的 x,请输出满足条件的x 的个数。
/* 分解质因数。可以得到gcd(x/a1,a0/a1)=1以及gcd(b1/x,b1/b0)=1。 假设对于一个质因数y,a0含有a0c个y,a1含有a1c个y,b0含有b0c个y,b1含有b1c个y。 所以如果a0c<a1c,无解;如果a0c=a1c,那么x至少含有a1c个y;如果a0c>a1c,那么x只可能含有a1c个y。 同理,如果b1c<b0c,无解;如果b1c=b0c,那么x至多含有b1c个y;如果b1c>b0c,那么x只可能含有b1c个y。 由此,可以求出对于每一个质数,x可能含有几个它,并求出一共有多少种选择方式。然后根据乘法原理,将每一个质数 */ #include<iostream> #include<fstream> using namespace std; int n,ans=0; struct{ int big,c[100],ci[100],tot; }a0,a1,b0,b1; int l[100],r[100]; bool p=1; int main() { cin>>n; while(n>0) { n--;p=1; cin>>a0.big>>a1.big>>b0.big>>b1.big; int x=2; b1.tot=0; while(b1.big>=x*x) { int len=0; while(b1.big%x==0) len++,b1.big/=x; if(len>0) { b1.c[++b1.tot]=x; b1.ci[b1.tot]=len; } x++; } if(b1.big!=1) { b1.c[++b1.tot]=b1.big; b1.ci[b1.tot]=1; } a0.tot=0; for(int i=1;i<=b1.tot;++i) { int len=0; while(a0.big%b1.c[i]==0) { len++; a0.big/=b1.c[i]; } a0.tot++; a0.c[a0.tot]=b1.c[i]; a0.ci[a0.tot]=len; } a1.tot=0; for(int i=1;i<=b1.tot;++i) { int len=0; while(a1.big%b1.c[i]==0) len++; a1.big/=b1.c[i]; a1.tot++; a1.c[a1.tot]=b1.c[i]; a1.ci[a1.tot]=len; } b0.tot=0; for(int i=1;i<=b1.tot;++i) { int len=0; while(b0.big%b1.c[i]==0) { len++; b0.big/=b1.c[i]; } b0.tot++; b0.c[b0.tot]=b1.c[i]; b0.ci[b0.tot]=len; } for(int i=1;i<=b1.tot;++i) { if(b1.ci[i]<a1.ci[i]) {p=0;break;} if(a1.ci[i]<a0.ci[i]&&b1.ci[i]>b0.ci[i]&&b1.ci[i]>a1.ci[i]) {p=0;break;} if(a1.ci[i]<a0.ci[i]) {l[i]=a1.ci[i];r[i]=a1.ci[i];continue;} if(b1.ci[i]>b0.ci[i]) {l[i]=b1.ci[i];r[i]=b1.ci[i];continue;} l[i]=a1.ci[i];r[i]=b1.ci[i]; } ans=1; for(int i=1;i<=b1.tot;++i) ans*=(r[i]-l[i]+1); if(p==0) ans=0; cout<<ans<<endl; } return 0; }
NOIP2009最优贸易
题目描述 Description
【问题描述】
C 国有n 个大城市和m 条道路,每条道路连接这n 个城市中的某两个城市。任意两个
城市之间最多只有一条道路直接相连。这m 条道路中有一部分为单向通行的道路,一部分
为双向通行的道路,双向通行的道路在统计条数时也计为1 条。
C 国幅员辽阔,各地的资源分布情况各不相同,这就导致了同一种商品在不同城市的价
格不一定相同。但是,同一种商品在同一个城市的买入价和卖出价始终是相同的。
商人阿龙来到 C 国旅游。当他得知同一种商品在不同城市的价格可能会不同这一信息
之后,便决定在旅游的同时,利用商品在不同城市中的差价赚回一点旅费。设C 国n 个城
市的标号从1~ n,阿龙决定从1 号城市出发,并最终在n 号城市结束自己的旅行。在旅游的
过程中,任何城市可以重复经过多次,但不要求经过所有n 个城市。阿龙通过这样的贸易方
式赚取旅费:他会选择一个经过的城市买入他最喜欢的商品——水晶球,并在之后经过的另
一个城市卖出这个水晶球,用赚取的差价当做旅费。由于阿龙主要是来C 国旅游,他决定
这个贸易只进行最多一次,当然,在赚不到差价的情况下他就无需进行贸易。
假设 C 国有5 个大城市,城市的编号和道路连接情况如下图,单向箭头表示这条道路
为单向通行,双向箭头表示这条道路为双向通行。
假设 1~n 号城市的水晶球价格分别为4,3,5,6,1。
阿龙可以选择如下一条线路:1->2->3->5,并在2 号城市以3 的价格买入水晶球,在3
号城市以5 的价格卖出水晶球,赚取的旅费数为2。
阿龙也可以选择如下一条线路 1->4->5->4->5,并在第1 次到达5 号城市时以1 的价格
买入水晶球,在第2 次到达4 号城市时以6 的价格卖出水晶球,赚取的旅费数为5。
现在给出 n 个城市的水晶球价格,m 条道路的信息(每条道路所连接的两个城市的编号
以及该条道路的通行情况)。请你告诉阿龙,他最多能赚取多少旅费。
输入描述 Input Description
第一行包含 2 个正整数n 和m,中间用一个空格隔开,分别表示城市的数目和道路的
数目。
第二行 n 个正整数,每两个整数之间用一个空格隔开,按标号顺序分别表示这n 个城
市的商品价格。
接下来 m 行,每行有3 个正整数,x,y,z,每两个整数之间用一个空格隔开。如果z=1,
表示这条道路是城市x 到城市y 之间的单向道路;如果z=2,表示这条道路为城市x 和城市
y 之间的双向道路。
输出描述 Output Description
包含1 个整数,表示最多能赚取的旅费。如果没有进行贸易,
则输出0。
样例输入 Sample Input
5 5
4 3 5 6 1
1 2 1
1 4 1
2 3 2
3 5 1
4 5 2
样例输出 Sample Output
5
数据范围及提示 Data Size & Hint
【数据范围】
输入数据保证 1 号城市可以到达n 号城市。
对于 10%的数据,1≤n≤6。
对于 30%的数据,1≤n≤100。
对于 50%的数据,不存在一条旅游路线,可以从一个城市出发,再回到这个城市。
对于 100%的数据,1≤n≤100000,1≤m≤500000,1≤x,y≤n,1≤z≤2,1≤各城市
水晶球价格≤100。
/* 找到一个价格最低和一个价格最高点(满足由起点能到达,且又可顺序到达终点),找最低价格的点可以这样来处理,从源点开始找到所有点的最低价格(某个顶点的最低价格就是从源点到该所有可能路径上的价格最低的点),最后枚举卖出的点,并且判断该点是否可以到达即可。 spfa处理出到某个点的路径上的最小买入价格以及其到终点路径上的最大卖出价格即可 */ #include<iostream> #include<cstring> #include<cstdio> using namespace std; int n,m,cnt; int v[100001]; int head1[100001],head2[100001]; int mn[100001],mx[100001]; int d[1000001]; bool pd[100001]; struct data{ int from,to,next1,next2; }e[1000001]; void insert(int u,int v) { cnt++; e[cnt].to=v;e[cnt].from=u; e[cnt].next1=head1[u];e[cnt].next2=head2[v]; head1[u]=cnt;head2[v]=cnt; } void spfa1() { int t=0,w=0; d[0]=1;mn[1]=v[1];pd[1]=1; while(t<=w) { int p=head1[d[t]]; while(p>0) { if(mn[e[p].to]>mn[d[t]]||v[e[p].to]<mn[e[p].to]) { mn[e[p].to]=min(v[e[p].to],mn[d[t]]); if(!pd[e[p].to])d[++w]=e[p].to; pd[e[p].to]=1; } p=e[p].next1; } pd[d[t]]=0; t++; } } void spfa2() { memset(pd,0,sizeof(pd)); memset(mx,-1,sizeof(mx)); int t=0,w=0; d[0]=n;mx[n]=v[n];pd[n]=1; while(t<=w) { int p=head2[d[t]]; while(p>0) { if(mx[e[p].from]<mx[d[t]]||v[e[p].from]>mx[e[p].from]) { mx[e[p].from]=max(v[e[p].from],mx[d[t]]); if(!pd[e[p].from])d[++w]=e[p].from; pd[e[p].from]=1; } p=e[p].next2; } pd[d[t]]=0; t++; } } int main() { memset(mn,127,sizeof(mn)); scanf("%d%d",&n,&m); for(int i=1;i<=n;i++) scanf("%d",&v[i]); for(int i=1;i<=m;i++) { int x,y,z; scanf("%d%d%d",&x,&y,&z); insert(x,y); if(z==2)insert(y,x); } spfa1();spfa2(); int ans=0; for(int i=1;i<=n;i++) ans=max(mx[i]-mn[i],ans); printf("%d",ans); return 0; }
NOIP2009靶形数独
2014年1月16日1,2321
题目描述 Description
小城和小华都是热爱数学的好学生,最近,他们不约而同地迷上了数独游戏,好胜的他
们想用数独来一比高低。但普通的数独对他们来说都过于简单了,于是他们向Z 博士请教,
Z 博士拿出了他最近发明的“靶形数独”,作为这两个孩子比试的题目。
靶形数独的方格同普通数独一样,在 9 格宽×9 格高的大九宫格中有9 个3 格宽×3 格
高的小九宫格(用粗黑色线隔开的)。在这个大九宫格中,有一些数字是已知的,根据这些
数字,利用逻辑推理,在其他的空格上填入1 到9 的数字。每个数字在每个小九宫格内不能
重复出现,每个数字在每行、每列也不能重复出现。但靶形数独有一点和普通数独不同,即
每一个方格都有一个分值,而且如同一个靶子一样,离中心越近则分值越高。
上图具体的分值分布是:最里面一格(黄色区域)为 10 分,黄色区域外面的一圈(红
色区域)每个格子为9 分,再外面一圈(蓝色区域)每个格子为8 分,蓝色区域外面一圈(棕
色区域)每个格子为7 分,最外面一圈(白色区域)每个格子为6 分,如上图所示。比赛的
要求是:每个人必须完成一个给定的数独(每个给定数独可能有不同的填法),而且要争取
更高的总分数。而这个总分数即每个方格上的分值和完成这个数独时填在相应格上的数字
的乘积的总和。如图,在以下的这个已经填完数字的靶形数独游戏中,总分数为2829。游
戏规定,将以总分数的高低决出胜负。
由于求胜心切,小城找到了善于编程的你,让你帮他求出,对于给定的靶形数独,能
够得到的最高分数。
输入描述 Input Description
一共 9 行。每行9 个整数(每个数都在0—9 的范围内),表示一个尚未填满的数独方
格,未填的空格用“0”表示。每两个数字之间用一个空格隔开。
输出描述 Output Description
输出可以得到的靶形数独的最高分数。如果这个数独无解,则输出整数-1。
样例输入 Sample Input
【输入输出样例 1】
7 0 0 9 0 0 0 0 1
1 0 0 0 0 5 9 0 0
0 0 0 2 0 0 0 8 0
0 0 5 0 2 0 0 0 3
0 0 0 0 0 0 6 4 8
4 1 3 0 0 0 0 0 0
0 0 7 0 0 2 0 9 0
2 0 1 0 6 0 8 0 4
0 8 0 5 0 4 0 1 2
【输入输出样例 2】
0 0 0 7 0 2 4 5 3
9 0 0 0 0 8 0 0 0
7 4 0 0 0 5 0 1 0
1 9 5 0 8 0 0 0 0
0 7 0 0 0 0 0 2 5
0 3 0 5 7 9 1 0 8
0 0 0 6 0 1 0 0 0
0 6 0 9 0 0 0 0 1
0 0 0 0 0 0 0 0 6
样例输出 Sample Output
【输入输出样例 1】
2829
【输入输出样例 1】
2852
数据范围及提示 Data Size & Hint
【数据范围】
40%的数据,数独中非0 数的个数不少于30。
80%的数据,数独中非0 数的个数不少于26。
100%的数据,数独中非0 数的个数不少于24。
/* 在输入的时候我们可以统计每一行已知的数,于是我们能够把没有填数的格子按每一行从小到大来排序,这样,枚举这个点能够扩展出来的搜索树就会比原来小 */ #include<cstdio> #include<iostream> #include<cstring> #include<algorithm> #define RE ((1<<9)-1) using namespace std; int sum_a[10]; struct T { int x,y; bool operator < (T t) const//已知数多的放在前面 { if(sum_a[x] == sum_a[t.x]) return x < t.x; return sum_a[x] > sum_a[t.x]; } }unknown[100];//保存没有填的格子的位置 int st[10][10]; int p[10][10];//权值数组 void init() { for(int i = 0; i < 9; i++) { p[0][i] = p[8][i] = p[i][0] = p[i][8] = 6; } for(int i = 1; i < 8; i++) { p[1][i] = p[7][i] = p[i][1] = p[i][7] = 7; } for(int i = 2; i < 7; i++) { p[2][i] = p[6][i] = p[i][2] = p[i][6] = 8; } for(int i = 3; i < 6; i++) { p[3][i] = p[5][i] = p[i][3] = p[i][5] = 9; } p[4][4] = 10; } int ans = -1; int cnt,sorce; int userow[10];//每一行使用的数 int usecol[10];//每一列使用的数 int usebox[10];//每一个九宫格内使用的数 void dfs(int cur) { if(cur > cnt) { ans = max(sorce,ans); return; } int x = unknown[cur].x; int y = unknown[cur].y; int temp = (userow[x]|usecol[y]|usebox[x/3*3+y/3]); if(temp == RE) return;//没有可以使用的数 for(int i = 1; i <= 9; i++) if(!((temp>>(i-1))&1)) { st[x][y] = i; usebox[x/3*3+y/3] |= (1<<(i-1)); userow[x] |= (1<<(i-1)); usecol[y] |= (1<<(i-1)); sorce += st[x][y]*p[x][y]; dfs(cur+1); sorce -= st[x][y]*p[x][y]; st[x][y] = 0; usebox[x/3*3+y/3] ^= (1<<(i-1)); userow[x] ^= (1<<(i-1)); usecol[y] ^= (1<<(i-1)); } } int main() { //freopen("sudoku.in","r",stdin); //freopen("sudoku.out","w",stdout); init(); for(int i = 0; i < 9; i++) { for(int j = 0; j < 9; j++) { scanf("%d",&st[i][j]); if(st[i][j] != 0) { sorce += st[i][j]*p[i][j]; userow[i] |= (1<<(st[i][j]-1)); usecol[j] |= (1<<(st[i][j]-1)); usebox[i/3*3+j/3] |= (1<<(st[i][j]-1)); ++sum_a[i];//统计i行已知的数 } } } for(int i = 0; i < 9; i++) for(int j = 0; j < 9; j++) { if(!st[i][j]) { ++cnt; unknown[cnt].x = i; unknown[cnt].y = j; } } sort(unknown+1,unknown+cnt+1); dfs(1); printf("%d\n",ans); }