以前在OJ上做的一些题目,总结了一下,写成结题报告,既是给自己的一个总结,也为其他想要参考的ACMer提供一个方便。
POJ 2192 Zipper
做这个题目,我是不记得交了多少次,只记得倒数第二次交的时候是个PE,因为我在最后一个输出语句的set d后面加了一个空格才打冒号。这说明什么嘞?说明我真的好粗心,我想是这样的。
题目是我以前做过的,那个时候用的是比较呆滞的方法,结果没有做出来,应该说,那个时候在交过两次之后,我自己已经发现了问题的所在,但因为那种方法的限 制,我没有能力对它再进行修改。所以,现在重新做的时候,是把以前写的那个全删了再重写的。当时是直接把第三个与第一个与第二个进行匹配,但其中有个问题 就是如果两个字符串如:cateb,cabte,那么程序在有两个或者更多个相同的字符的情况下,只能对其前一个重新判断,也就是说像这种有两个相同但又 要取后者的话,就无能为力了。面对这个问题,我也尝试着用回溯做过,但好像并没有起什么作用,最后还是WA了。现在用的这种方法不晓得叫什么名字好,虽然 这是个DP题目,但我还是觉得这种方法不算DP,最多就是说借用了几个数字跟DP里那个保存状态可以扯上一点点关系。大概的思路是这样的:将第三个字符串 与第一个跟第二个字符串进行匹配,如果只有一个能够匹配上,就让这个匹配,如果两个都能够匹配,那就把它们放入栈中,而取第一个进行匹配,等到以后不能匹 配的时候,就说明当时取第一个进行匹配是错误的,然后就把它们从栈中重新取出,跟第二个进行匹配。这个时候如果栈为空,刚说明第三个不能够跟第一个和第二 个的组合成功匹配,输出“no”的信息,退出,进行下次的输入。
代码如下:
#include <iostream>
#include <string>
using namespace std;
char first[201], second[201], third[401];
int fistack[200], sestack[200], thstack[200], top;
int main()
{
int n, num = 1;
cin>>n;
while(num <= n){
// cin>>first>>second>>third;
scanf("%s%s%s", first, second, third);
int filen = strlen(first), selen = strlen(second), thlen = strlen(third);
top = 0;
bool flag = true;
//本来是没有这个操作的,但交了两次之后发现超时了,然后就只能在这里进行一个剪枝了
for(int i = 0; i < filen; i++)
fistack[first[i]-'A']++;
for(int i = 0; i < selen; i++)
sestack[second[i]-'A']++;
for(int i = 0; i < thlen; i++)
thstack[third[i]-'A']++;
for(int i = 0; i < 26; i++){
if(fistack[i]+sestack[i] != thstack[i]){
printf("Data set %d: no\n", num++);
flag = false;
break;
}
if(fistack[i+32]+sestack[i+32] != thstack[i+32]){
printf("Data set %d: no\n", num++);
flag = false;
break;
}
}
int i = 0, j = 0, k = 0;
while(flag && (k < thlen)){
if((first[i] == third[k]) && (second[j] == third[k])){
fistack[++top] = i;
sestack[top] = j;
i++; k++; //两个相同时,默认让第一个与第三个进行匹配
continue;
}
if((first[i] == third[k]) && (second[j] != third[k])){
i++; k++;
continue;
}
if((first[i] != third[k]) && (second[j] == third[k])){
j++; k++;
continue;
}
if(top > 0){
i = fistack[top];
j = sestack[top]+1; //这时说明默认的第一个与第三个匹配不成功,因而改为让第二个与第三个匹配
k = i+j;
top--;
}
else{
printf("Data set %d: no\n", num++);
// cout<<"Data set "<<num++<<" : no"<<endl;
flag = false;
break;
}
}
if(flag)
printf("Data set %d: yes\n", num++);
// cout<<"Data set "<<num++<<" : yes"<<endl;
memset(fistack, 0, sizeof(fistack));
memset(sestack, 0, sizeof(sestack));
memset(thstack, 0, sizeof(thstack));
}
return 0;
}
POJ 2479 Maximum sum
应该说这是一个很纯正的动态规划,开始的时候我是用的O(N^2)的动态规划做的,结果很显然地超时了。现在想想,那个时候怎么会这样去想嘞,数据给出的 就是50000,O(N^2)的算法应该是很显然会超时的,但无论怎样,我还是对这个题目做得蛮有感觉的,毕竟那种O(N^2)的算法完全是凭借自己的想 法做出来得,也算对自己的一点点安慰吧。O(N^2)的算法思路比较明显,在外层循环计算从左到右的最大值的时候,用内层循环来计算从右到左的最大值,其 中求最大值的步骤就用DP的思想。O(N^2)的代码如下:
#include <iostream>
using namespace std;
int ans[50001], num[50001], tmp[50001];
int main()
{
int tcase, n;
scanf("%d", &tcase);
int i, j, sum, max;
while(tcase--)
{
scanf("%d", &n);
for(i = 1; i <= n; i++)
scanf("%d", &num[i]);
ans[0] = 0;
sum = -999999999;
for(i = 1; i < n; i++)
{
if(ans[i-1] > 0)
ans[i] = ans[i-1]+num[i];
else
ans[i] = num[i];
tmp[i] = 0;
max = -999999999;
for(j = i+1; j <= n; j++)
{
if(tmp[j-1] > 0)
tmp[j] = tmp[j-1]+num[j];
else
tmp[j] = num[j];
if(max < tmp[j])
max = tmp[j];
}
// cout<<"ans[i] is "<<ans[i]<<" max is "<<max<<endl;
if(ans[i]+max > sum)
sum = ans[i]+max;
// cout<<"sum is "<<sum<<endl;
}
cout<<sum<<endl;
}
return 0;
}
后来,这样超时了,于是寻找O(N)的动规,最后得出的方法就是:在输入的时候,进行一次DP,求从左到右的最大值,再一次for循环,从右到左求最大值,同时不断更新最后要求的结果,等到第二次的for循环结束的时候,要求的最大值也就出来了。
O(N)的DP代码如下:
#include <iostream>
using namespace std;
int array[50001], num[50001];
const int MIN = -999999999;
int main()
{
int tcase, n;
cin>>tcase;
int tmp, ans, i, sum;
while(tcase--){
scanf("%d", &n);
tmp = MIN; sum = 0;
for(i = 1; i <= n; i++){
scanf("%d", &num[i]);
sum += num[i];
if(sum > tmp)
tmp = sum;
array[i] = tmp;
if(sum < 0)
sum = 0;
}
tmp = ans = MIN;
sum = 0;
for(i = n; i > 1; i--){
sum += num[i];
if(sum > tmp)
tmp = sum;
if(ans < (array[i-1]+tmp))
ans = array[i-1]+tmp;
if(sum < 0)
sum = 0;
}
cout<<ans<<endl;
}
return 0;
}
TOJ 3025 Once Around the Lock
当时我跟我的另外一个队友都在考虑这个题目,本来是他在做的,后面我做完我的之后,发现已经4点40了,我以为是5点结 束,就没想再做了。于是帮他一起想测试数据,结果弄到5点多很多了,发现大家还在做,才知道是6点结束,于是自己也开始真正对这个题目进行思考。由于他给 我大概地讲了一下题目的意思,我也就只是把整个题目看了一下,没有仔细思考题目的意思,结果就因为这个让我们都犯了严重的错误,对题目的理解没到位,后面 怎么做都不对。在听了别人的解释之后,我把自己之前写的全部删掉,重新做了一次。
题目应该可以这样理解:要寻找三次组合使转动后的结果是两个位置相同,但可以相互重合,也就是说可以到了第二步,然后发现这一步行不通了,但符合第一步的 条件,这个时候并不能判断就是Closed了(我们之前都这样想的),它可以又从第一步开始进行整个过程,也就是结束唯一条件就是“?“,只要在这个之前 刚好是连续的C、CC、C但三个转动后位置与给定的位置相同就可以了。当然,这其中的C和CC都可以是一连串的C或者CC组合,只是不能相互杂合在一起。
下面我的代码:
#include <iostream>
#include <string>
using namespace std;
int main()
{
int n, x, y, z, k = 1, turn, //一次输入的移动数
sum, top, times;
bool last;
char flag[3];
while(cin>>n){
if(!n)
break;
cin>>x>>y>>z;
//init
sum = 0; top = 0; times = 0; last = 0;
while(cin>>flag){
if(flag[0] == '?')
break;
if(!strcmp(flag, "C")){ // C turn
cin>>turn;
if(!last){ //last turn is CC
last = true;
top -= turn;
while(top < 0)
top += n;
sum = turn;
if((times == 2) && (top == z) && (sum <= n))
times = 3;
if((times == 2) && (sum > n))
times = 0;
if((times == 0) && (top == x) && (sum >= n))
times = 1;
if((times == 2) && (top == x) && (sum >= n))
times = 1;
if((times == 1) && (top != x))
times = 0;
if((times == 1) && (sum < n))
times = 0;
}
else{ //last turn is C
top -= turn;
while(top < 0)
top += n;
sum += turn;
if((times == 0) && (top == x) && (sum >= n))
times = 1;
if((times == 1) && (top != x))
times = 0;
if((times == 3) && (top == x) && (sum >= n))
times = 1;
if(times == 3)
times = 0;
else if((times == 2) && (top == z) && (sum <= n))
times = 3;
else if((times == 2) && (top == x) && (sum >= n))
times = 1;
else if((times == 2) && (sum >= n))
times = 0;
}
}
else{ //CC turn
cin>>turn;
if(last){ //last turn is C
top += turn;
top %= n;
sum = turn;
last = false;
if(times == 2)
times = 0;
if((times == 1) && (sum <= 2*n) && (sum > n) && (top == y))
times = 2;
if(times == 3)
times = 0;
}
else{ //last turn is CC
top += turn;
top %= n;
sum += turn;
if(times == 2)
times = 0;
if((times == 1) && (sum <= 2*n) && (sum > n) && (top == y))
times = 2;
}
}
}
if(times == 3)
cout<<"Case "<<k++<<": Open"<<endl;
else
cout<<"Case "<<k++<<": Closed"<<endl;
}
return 0;
}
TOJ 3027 And Now, a Remainder from Our Sponsor
这是那天比赛我做的另外一个题目,题目的意思就不说了,还是蛮容易看懂得。整个过程也相对比较简单,就是由输入的一个七位 或者八位数跟前面的四个数求一个五位或者六位数,让这个数对那四个数求模得到的数的组合就是输入的数,不过需要注意其中位数不同以及0的问题,最后,不要 把结果中最后的空格输出。TOJ总喜欢干这事,不小心的人就总会得到PE。
#include <iostream>
#include <string>
using namespace std;
int keys[4], tmp[4];
char message[50][10];
char ans[152];
int main()
{
int tcase, n, i;
cin>>tcase;
int tmp2, max, maxTag, code, ansTag;
bool tag;
while(tcase--){
scanf("%d", &n);
max = 0; maxTag = 0;
for(i = 0; i < 4; i++){
scanf("%d", &keys[i]);
if(keys[i] > max){
max = keys[i];
maxTag = i;
}
}
ansTag = 0;
for(i = 0; i < n; i++){
tag = true;
scanf("%s", message[i]);
if(strlen(message[i]) == 7)
tag = false;
if(tag)
tmp[0] = (message[i][0]-'0')*10+message[i][0+tag]-'0';
else
tmp[0] = message[i][0]-'0';
tmp[1] = (message[i][1+tag]-'0')*10+message[i][2+tag]-'0';
tmp[2] = (message[i][3+tag]-'0')*10+message[i][4+tag]-'0';
tmp[3] = (message[i][5+tag]-'0')*10+message[i][6+tag]-'0';
//求code
for(code = 10000; ; code++){
if(code%max == tmp[maxTag])
break;
}
while(true){
if((code%keys[0]==tmp[0]) && (code%keys[1]==tmp[1]) && (code%keys[2]==tmp[2]) && (code%keys[3]==tmp[3]))
break;
code += max;
}
tmp2 = code/100000; code %= 100000;
tmp2 = tmp2*10+code/10000; code %= 10000;
if(tmp2 == 27) ans[ansTag++] = ' ';
else ans[ansTag++] = tmp2-1+'A';
tmp2 = code/1000; code %= 1000;
tmp2 = tmp2*10+code/100; code %= 100;
if(tmp2 == 27) ans[ansTag++] = ' ';
else ans[ansTag++] = tmp2-1+'A';
tmp2 = code/10; code %= 10;
tmp2 = tmp2*10+code;
if(tmp2 == 27) ans[ansTag++] = ' ';
else ans[ansTag++] = tmp2-1+'A';
}
ansTag--;
while(ans[ansTag] == ' ')
ansTag--;
for(i = 0; i <= ansTag; i++)
printf("%c", ans[i]);
printf("\n");
}
return 0;
}
TOJ 3021 CIVIC DILL MIX
这个题目是比赛的时候我做的一个题目,当时只求快点把它做完,好去做其他的题目,也就没管太多的时间或者空间效率了,可以优化的地方应该是有很多很多吧!
我当时的想法是:整个程序分为三个模块,一个就是main函数,另外一个就是把一个阿拉伯数字转化成罗马字符串(这个函数就叫做toToman),最后一 个把罗马字符串转化成阿拉伯数字(这个函数叫做toNormal)。toNormal这个功能相对而言要简单很多,直接从左到右遍历一次就好了,只有一种 特殊情况,就是有可能左边的那个数会作为被右边的数减去的数,所以遍历的时候需要考虑一下遍历到的数的右边的数的情况。toRoman的话,我没有想太 多,就直接做了,如果这个数比1000大就不断地进行减1000操作,直到它小于1000,如果还是比900大,那么就转化成CM的形式;else if比500大,就从中减掉一个500;else if比400大,就要化成CD的形式。再重复上面的操作,但把上面的数都除以10。整个过程就相当于对9、5、4这三个特殊的数进行操作的过程。
下面是我写的代码:
#include <iostream>
#include <string>
using namespace std;
char instr[100], outstr[100];
int letter[26] = {0,0,100,500,0,0,0,0,1,0,0,50,1000,0,0,0,0,0,0,0,0,5,0,10,0,0};
int toNormal()
{
int i = 0, length = strlen(instr), ans = 0;
if(length == 1)
{
ans = letter[instr[0]-'A'];
return ans;
}
while(i < length-1){
if(letter[instr[i]-'A'] < letter[instr[i+1]-'A']){
ans -= letter[instr[i]-'A'];
}
else
ans += letter[instr[i]-'A'];
i++;
}
ans += letter[instr[length-1]-'A'];
return ans;
}
void toRoman(int n)
{
int tag = 0;
while(n >= 1000){
outstr[tag++] = 'M';
n -= 1000;
}
if(n >= 900){
outstr[tag++] = 'C'; outstr[tag++] = 'M';
n -= 900;
}
else if(n >= 500){
outstr[tag++] = 'D'; n -= 500;
}
else if(n >= 400){
outstr[tag++] = 'C'; outstr[tag++] = 'D';
n -= 400;
}
while(n >= 100){
outstr[tag++] = 'C'; n -= 100;
}
if(n >= 90){
outstr[tag++] = 'X'; outstr[tag++] = 'C';
n -= 90;
}
else if(n >= 50){
outstr[tag++] = 'L'; n -= 50;
}
else if(n >= 40){
outstr[tag++] = 'X'; outstr[tag++] = 'L';
n -= 40;
}
while(n >= 10){
outstr[tag++] = 'X'; n -= 10;
}
if(n == 9){
outstr[tag++] = 'I'; outstr[tag++] = 'X';
n -= 9;
}
else if(n >= 5){
outstr[tag++] = 'V'; n -= 5;
}
else if(n == 4){
outstr[tag++] = 'I'; outstr[tag++] = 'V';
n -= 4;
}
while(n >= 1){
outstr[tag++] = 'I'; n--;
}
outstr[tag] = '\0';
}
int main()
{
int n, k = 1, sum, i;
while(scanf("%d", &n) != EOF){
if(!n)
break;
sum = 0;
for(i = 0; i < n; i++){
scanf("%s", instr);
sum += toNormal();
}
toRoman(k);
printf("Case %s: ", outstr);
toRoman(sum);
printf("%s\n", outstr);
k++;
}
return 0;
}
POJ 3505 Tower Parking (TOJ 3037)
可能是我的英语水平太有限了吧,这个题目我硬是看了好久然后别人跟我大概地说了下,我才把题意弄懂。就是说一个类似于停车 库的东西里面放有许多车子,这个停车库有很多层,每一层又类似于一个转盘,或者说在每一层有一个转盘,可以通过顺时针或者逆时针转动把车子移动到电梯口, 然后把车子运到第一层的出口给取车的人,在整幢停车库里,车子是有编号的(从1到车子的数目N),也就是说取车的时候要先取1,然后取2,最后再取N,求完成整个过程的时间。
我是这样想的:在输入数据的时候对数据进行一下预处理,如果该位置的编号为-1,也就是说这个地方没有停车,就不管它;如果该位置有不为-1的编号,假设 编号为M,那么这辆车就要在第M次取车的时候被取走,我就把这辆车初始所在位置记录在下标为M的数组中。这样预处理之后,第一次要被取的车的信息就存放在 数组下标为1的元素中,第M次要被取的车的信息就存放在数组下标为M的元素中。另外再建一个数组用来保存每层顺时针或者逆时针转动的信息,最后取车的时候 被取的车的位置就由这两个数组的信息来唯一确定。
下面是我写的代码:
#include <iostream>
using namespace std;
struct Node{
int x, y;
}pos[2501];
int turn[50];
int main()
{
int tcase, height, length;
cin>>tcase;
while(tcase--){
cin>>height>>length;
int total = 0, tmp;
for(int i = 0; i < height; i++)
for(int j = 0; j < length; j++){
cin>>tmp;
if(tmp != -1){
total++;
pos[tmp].x = j;
pos[tmp].y = i;
}
}
//end of input
int k = 1;
int curX, curY, ans = 0;
while(k <= total){
curY = pos[k].y; curX = pos[k].x+turn[curY];
if(curX >= length)
curX %= length;
while(curX < 0)
curX += length;
//cout<<"curX is "<<curX<<" curY is "<<curY<<endl;
if(curX > length-curX){
ans += (length-curX)*5+curY*20;
turn[curY] += length-curX;
}
else{
ans += curX*5+curY*20;
turn[curY] -= curX;
}
k++;
//cout<<"ans is "<<ans<<endl;
}
cout<<ans<<endl;
memset(turn, 0, sizeof(turn));
}
return 0;
}