SGU 153 解题手记
用f[i]表示有i根火柴时,当前人是赢还是输,那么f[i]是只与f[i-1], f[i-p[1]], f[i-p[2]], ... f[i-p[m]]有关的,如果这些状态都是赢,那么f[i]就是输,否则f[i]是赢。
直接这样递推到N就要TLE了。但考虑距离f[i]最远且与之相关的点是f[i-max(p[i])],把他们之间的状态连起来,写成(f[i-max(p[i])],...,f[i-1],f[i])这样,f中用0/1表示输赢,则连起来的状态是个二进制数,如果这个二进制数重复出现,则整个f序列就会出现循环。循环节的长度加上不循环部分的长度不会超过2^max(p[i])。
Submit 1: WA on 4。我用pos[x]=i(x是(f[i-9],...,f[i-1],f[i])对应的二进制数)表示(f[i-9],...,f[i-1],f[i])这个状态第一次在i这个位置出现,那么就有个边界值——pos[x]是不小于9的。另外,找到循环节后立刻退出递推即可。
Submit 2: WA on 4。循环节的长度加上不循环部分的长度应该是不超过2^max(p[i]+1)+max(p[i]+1),程序实现中应该是1034。
Submit 3: WA on 4。把f中的状态连起来时,只需要(f[i-9],...,f[i-1])就够了。边界值——pos[x]是不小于10的,循环节的长度加上不循环部分的长度应该是不超过2^9+10+1。用pos[x]=i(x是(f[i-9],...,f[i-1])对应的二进制数)表示(f[i-9],...,f[i-1])这个状态第一次在i这个位置出现,当x在j位置再次出现时,表示i到j-1是循环节。题目中给出的P1,P2,...,Pm并不是有序的。
Submit 4: WA on 4。if (N<=l2) r=f[N]; 这句有问题,f[l2]没有意义。
Submit 5: WA on 4。r=f[(N-l1)%(l2-l1)+l1]; 这个也要改成 r=f[(N-l1)%(l2-1-l1)+l1]; 这里问题的原因跟Submit 4是一样的,l2并不是循环节末端,而是“超出末端”,循环节是l1...l2-1。
Submit 6: AC。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | #include <cstdio> #include <cstring> #include <algorithm> using namespace std; int main() { int K,N,m,p[9]={1},pos[512],x,l1,l2; bool f[524],r; scanf("%d",&K); for (;K;--K) { memset(pos,0,sizeof(pos)); memset(f,0,sizeof(f)); x=0; l1=0; l2=2; scanf("%d%d",&N,&m); for (int i=1;i<=m;++i) scanf("%d",&p[i]); sort(p,p+m+1); for (;!l1;++l2) { f[l2]=1; for (int j=0;j<=m && l2-p[j]>0 && f[l2];++j) f[l2]&=f[l2-p[j]]; f[l2]=!f[l2]; if (pos[x]) l1=pos[x]; else if (l2>9) pos[x]=l2; x=(x<<1)&0x1ff|f[l2]; } if (N<l2) r=f[N]; else r=f[(N-l1)%(l2-1-l1)+l1]; if (r) printf("FIRST PLAYER MUST WIN\n"); else printf("SECOND PLAYER MUST WIN\n"); } return 0; } |

2 Responses
我也是wa on 4,可否提供一个会wa的数据
Reply
test case 4可能是个强数据,可以看到我WA on 4是因为多个原因引起的。
Reply