一、題目
二、解法
這類翻棋子游戲有一個套路,可以把原遊戲看成翻每一個棋子的組合遊戲。
想要求出每一個子遊戲的函數值,轉移時可以繼續把後面的棋子拆成若干個子游戲,設爲第個位置的函數,轉移就是函數前綴和。
打表可以發現取值相同時函數值相同,其實這也不難意會,因爲相同,所以後面能翻棋子的分佈是差不多的,這就導致遊戲會產生相同的結果(都說是意會,這麼認真幹嘛)
考慮分塊做法相同的劃分成一塊,再用分成兩部分考慮,小於的部分直接存,大於的部分存。具體轉移的時候只需要考慮每個塊,設這個塊爲,假設已經確定了左端點,可以先找到相等的右端點,然後就可以算真正的右端點(滿足整除於),設函數的前綴和爲,轉移塊的函數爲,那麼是一定取得到,這時候我們還要考慮中能被整除的個數,如果爲奇數就把前綴和異或上。
做法的複雜度作者不是特別清楚,但是隻要注意卡常,還是足以通過此題。
#include <cstdio>
#include <cmath>
const int M = 500005;
int read()
{
int x=0,flag=1;char c;
while((c=getchar())<'0' || c>'9') if(c=='-') flag=-1;
while(c>='0' && c<='9') x=(x<<3)+(x<<1)+(c^48),c=getchar();
return x*flag;
}
int n,m,k,t,nn,a[M],b[M],q[M],g[M],tag[M];
void init()
{
for(int l=1,r;l<=n;l=r+1)
r=n/(n/l),q[++t]=r;
for(int i=t;i>=1;i--)
{
int x=q[i],res=0,cnt=0,y=1;
for(int l=2*x,r;l<=n;l=r+x)
{
r=n/(n/l)/x*x;
int z=l<nn?a[l]:b[n/l];
tag[res^z]=1;
g[++cnt]=res^z;
if(((r-l)/x+1)&1) res^=z;
}
while(tag[y]) y++;
x<nn?a[x]=y:b[n/x]=y;
for(int j=1;j<=cnt;j++) tag[g[j]]=0;
}
}
int main()
{
n=read();m=read();
nn=sqrt(n);
init();
while(m--)
{
k=read();
int res=0;
for(int i=1;i<=k;i++)
{
int x=read();
res^=(x<nn?a[x]:b[n/x]);
}
if(res) puts("Yes");
else puts("No");
}
}