NOIP模拟69
T1 石子游戏
解题思路
首先一个结论是对于最大取石子数量的限制 \(x\) 将所有石子个数分别 \(\bmod\;x+1\) 再 \(xor\) 最后的结果如果是 0 的话,面临这个局面的人必败,否则必胜。
有点像 巴什博弈 (尽管我也是第一次接触。。),一堆与多堆的情况类似。。
当 \(n=m+1\) 时,由于一次最多只能取 \(m\) 个,所以无论先取者拿走多少个,后取者都能够一次拿走剩余的物品,后者取胜,所以当一方面对的局势是 \(n\bmod (m+1)=0\) 时,其面临的是必败的局势。所以当 \(n=(m+1)\times r+s\) , \((r为任意自然数,s\le m)\) 时,如果先取者要拿走 \(s\) 个物品,如果后取者拿走 \(x(\le m)\) 个,那么先取者再拿走 \(m+1-k\) 个,结果剩下 \((m+1)\times(r-1)\) 个,以后保持这样的取法,那么先取者肯定获胜。总之,要保持给对手留下 \(m+1\) 的倍数,就能最后获胜。
然后就是优化了,对于每一位考虑贡献,预处理 \(cnt_x\) 表示为 \(x\) 的数字的个数 \(f_{i,j}\) 表示满足 \(x-i\) 有 \(j\) 这个二进制位的数的个数。
就有 \(f_{i,j}=f_{i+2^{j+1},j}+\sum\limits_{k=i+2^j}^{i+2^{j+1}-1}cnt_k\)
然后对于每一个不同的区间 \([k\times(x+1),(k+1)\times(x+1)\;)\) 计算答案。
但是这并不是简单的前缀加减,柿子应该是这样的
原因吗,显然再 \(j+1\) 这个二进制位的差是对于 \(j\) 没有影响的。
同样的代码中的 \(+2^j\) 这个区间中变动的都是 \(j\) 这个二进制位之前的也没有什么影响。
在处理一下边界问题就好了。
code
#include <bits/stdc++.h>
#define int long long
#define ull unsigned long long
#define f() cout<<"Failed"<<endl
using namespace std;
inline int read()
{
int x=0,f=1;char ch=getchar();
while(ch>'9'||ch<'0'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
return x*f;
}
const int N=1e6+10,Lg=20;
int n,lg,s[N],cnt[N],pre[N],f[N][Lg];
signed main()
{
freopen("stone.in","r",stdin); freopen("stone.out","w",stdout);
n=read(); lg=log2(n)+1;
for(int i=1;i<=n;i++) s[i]=read(),cnt[s[i]]++;
for(int i=1;i<=n;i++) pre[i]=pre[i-1]+cnt[i];
for(int i=n;i>=0;i--)
for(int j=0;i+(1ll<<j)<=n;j++)
f[i][j]=f[min(n+1,i+(1ll<<j+1))][j]+pre[min(n,i+(1ll<<j+1)-1)]-pre[i+(1ll<<j)-1];
for(int i=1;i<=n;i++)
{
int lim=n/(i+1);
for(int j=0;j<=lg;j++)
{
int sum=0;
for(int k=0;k<=lim;k++)
{
int l=k*(i+1),r=min(n,(k+1)*(i+1)-1),temp=(r-l+1)>>j+1;
int tot=f[l][j]-f[l+temp*(1ll<<j+1)][j];
if(l+temp*(1ll<<j+1)+(1ll<<j)-1<r) tot+=pre[r]-pre[l+temp*(1ll<<j+1)+(1ll<<j)-1];
sum+=tot;
}
if(sum&1){printf("Alice ");goto V;}
}
printf("Bob "); V:;
}
return 0;
}
T2 大鱼吃小鱼
解题思路
set
+桶可以得到 60pts (code)
线段树上二分每一次优先递归右区间从右区间贪心选择,并且记录下更改过的值,在处理完答案之后再复原回去。
处理的时候类似于区间覆盖 laz 标记。
同时用 set 维护一个有序数组用于查看当前值的下一个应该选哪个
code
#include <bits/stdc++.h>
#define int long long
#define ull unsigned long long
#define f() cout<<"Failed"<<endl
#define ls x<<1
#define rs x<<1|1
using namespace std;
inline int read()
{
int x=0,f=1;char ch=getchar();
while(ch>'9'||ch<'0'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
return x*f;
}
const int N=8e6+10,INF=1e18;
int n,m,ans,top,now,cnt,sta[N],s[N],lsh[N];
struct Ques{int opt,x,y;}q[N];
struct Node{int dat,bas,laz;}ys[N];
multiset<int> res;
bool can=true,jud=true;
struct Segment_Tree
{
bool vis[N<<2]; Node tre[N<<2];
int ceil(int x,int y){return x/y+(x%y!=0);}
void push_up(int x)
{
if(can&&!vis[x]) sta[++top]=x,ys[top]=tre[x],vis[x]=true;
tre[x].dat=tre[ls].dat+tre[rs].dat;
tre[x].bas=tre[ls].bas+tre[rs].bas;
}
void push_down(int x)
{
if(!tre[x].laz) return ;
if(!vis[ls]) sta[++top]=ls,ys[top]=tre[ls],vis[ls]=true;
if(!vis[rs]) sta[++top]=rs,ys[top]=tre[rs],vis[rs]=true;
tre[ls].dat=tre[rs].dat=tre[ls].bas=tre[rs].bas=0; tre[ls].laz=tre[rs].laz=1; tre[x].laz=0;
}
void insert(int x,int l,int r,int pos,int val)
{
if(l==r) return tre[x].bas+=val,tre[x].dat+=val*lsh[l],void();
int mid=(l+r)>>1;
if(pos<=mid) insert(ls,l,mid,pos,val);
else insert(rs,mid+1,r,pos,val);
push_up(x);
}
int query(int x,int l,int r,int L,int R)
{
if(L>R||!tre[x].dat) return 0;
if(L<=l&&r<=R) return tre[x].dat;
int mid=(l+r)>>1,sum=0; push_down(x);
if(L<=mid) sum+=query(ls,l,mid,L,R);
if(R>mid) sum+=query(rs,mid+1,r,L,R);
return sum;
}
void solve(int x,int l,int r,int L,int R,int &need)
{
if(l==r)
{
if(tre[x].dat<need) goto V;
if(!vis[x]) sta[++top]=x,ys[top]=tre[x],vis[x]=true;
int temp=ceil(need,lsh[l]);
tre[x].bas-=temp; ans+=temp;
now+=temp*lsh[l]; need-=temp*lsh[l];
jud=false; tre[x].dat=tre[x].bas*lsh[l];
return ;
} V:;
if(r<=R&&tre[x].dat<=need)
{
need-=tre[x].dat; now+=tre[x].dat;
if(need<=0) jud=true; ans+=tre[x].bas;
if(!vis[x]) sta[++top]=x,ys[top]=tre[x],vis[x]=true;
tre[x].dat=tre[x].bas=0; tre[x].laz=1; return ;
}
int mid=(l+r)>>1; push_down(x);
if(R<=mid) return solve(ls,l,mid,L,R,need),push_up(x),void();
if(jud) solve(rs,mid+1,r,L,R,need);
if(jud) solve(ls,l,mid,L,R,need);
push_up(x);
}
}T;
void Restore()
{
for(int i=1;i<=top;i++)
T.tre[sta[i]]=ys[i],
T.vis[sta[i]]=false,T.tre[sta[i]].laz=0;
}
void solve(int bas,int goal)
{
if(bas>=goal) return printf("0\n"),void();
int temp=goal-bas,rec,need; now=bas; ans=top=0;
while(temp>0&&now<goal)
{
auto it=res.lower_bound(now);
if(it!=res.end()) rec=*it;
else rec=INF;
need=min(temp,rec-now+1); jud=true;
int las=need,pos=lower_bound(lsh+1,lsh+cnt+1,now)-lsh-1;
if(!pos) break;
T.solve(1,1,cnt,1,pos,need);
if(need>0||it==res.end()) break;
temp-=las-need;
}
Restore();
if(need<=0) printf("%lld\n",ans); else printf("-1\n");
}
signed main()
{
freopen("fish.in","r",stdin); freopen("fish.out","w",stdout);
n=read();
for(int i=1;i<=n;i++) s[i]=read(),lsh[++cnt]=s[i],res.insert(s[i]);
m=read();
for(int i=1;i<=m;i++)
{
q[i].opt=read(); q[i].x=read();
if(q[i].opt==1) q[i].y=read();
lsh[++cnt]=q[i].x;
}
lsh[++cnt]=INF; sort(lsh+1,lsh+cnt+1); cnt=unique(lsh+1,lsh+cnt+1)-lsh-1; can=false;
for(int i=1;i<=n;i++) T.insert(1,1,cnt,lower_bound(lsh+1,lsh+cnt+1,s[i])-lsh,1);
can=true;
for(int i=1;i<=m;i++)
{
int opt=q[i].opt,x=q[i].x,y=q[i].y;
if(opt==1){solve(x,y);continue;}
if(opt==2)
{
can=false; T.insert(1,1,cnt,lower_bound(lsh+1,lsh+cnt+1,x)-lsh,1);
res.insert(x); can=true; continue;
}
can=false; T.insert(1,1,cnt,lower_bound(lsh+1,lsh+cnt+1,x)-lsh,-1);
res.erase(res.find(x)); can=true;
}
return 0;
}
T3 黑客
解题思路
大水题。。
对于 \([1,999]\) 区间内任意两个互质的数,分别求出在 \([a,b],[c,d]\) 两个区间中的上下界。
取一个交集乘上对应的贡献就是答案了。
code
#include <bits/stdc++.h>
#define int long long
#define ull unsigned long long
#define f() cout<<"Failed"
using namespace std;
inline int read()
{
int x=0,f=1;char ch=getchar();
while(ch>'9'||ch<'0'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
return x*f;
}
const int N=1e3+10,mod=1e9+7;
int a,b,c,d,ans;
vector<pair<int,int> > v;
inline void add(int &x,int y){x+=y;if(x>mod)x-=mod;}
signed main()
{
freopen("hacker.in","r",stdin); freopen("hacker.out","w",stdout);
a=read(); b=read(); c=read(); d=read();
for(int i=1;i<=999;i++)
for(int j=1;j<=999-i;j++)
if(__gcd(i,j)==1)
v.push_back(make_pair(i,j));
for(int i=0;i<v.size();i++)
{
int x=v[i].first,y=v[i].second;
int lima=ceil((1.0*a)/(1.0*x)),limb=floor((1.0*b)/(1.0*x));
int limc=ceil((1.0*c)/(1.0*y)),limd=floor((1.0*d)/(1.0*y));
int l=max(lima,limc),r=min(limb,limd);
if(l<=r) add(ans,(r-l+1)%mod*(x+y)%mod);
}
printf("%lld",ans);
return 0;
}
T4 黑客(续)
解题思路
丧心病狂考高精。。(改题的时候是我第一次打高精,竟然一遍过??)
数位 DP ,DP 方式有两种:传当前数字的状态或者对于后面数字的限制。
显然第二种 DP 复杂度是优于第一种的,但是第一种直接 __int128
强行压 17 位竟然可以过掉。。
经过本人实测倒数第三个测试点 dfs 函数 方法一调用了大约 25000
次,而方法二竟然只调用了 199
次。
可谓天壤之别,剩下的就是板子了。。
code
#include <bits/stdc++.h>
#define ull unsigned long long
#define f() cout<<"Failed"
using namespace std;
inline int read()
{
int x=0,f=1;char ch=getchar();
while(ch>'9'||ch<'0'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
return x*f;
}
const int N=5e2+10,M=1000;
int n,m,k,can[10];
bool vis[10][10],pas[N][1<<9];
struct Node
{
int a[M+10];
void clear(){memset(a,0,sizeof(a));}
inline Node friend operator << (Node x,int val)
{
if(!val) return x;
int lim=M; while(lim&&!x.a[lim]) lim--;
for(int i=lim;i>=1;i--) x.a[i+val]=x.a[i];
for(int i=1;i<=val;i++) x.a[i]=0; return x;
}
inline Node friend operator * (Node x,int y)
{
int lim=M; while(lim&&!x.a[lim]) lim--;
Node z; z.clear(); lim+=10;
for(int i=1;i<=lim;i++) z.a[i]=x.a[i]*y;
for(int i=1;i<=lim;i++) z.a[i+1]+=z.a[i]/10,z.a[i]%=10;
return z;
}
inline Node friend operator + (Node x,Node y)
{
int lim=M; while(lim&&!x.a[lim]&&!y.a[lim]) lim--;
Node z; z.clear(); lim++;
for(int i=1;i<=lim;i++) z.a[i]=x.a[i]+y.a[i];
for(int i=1;i<=lim;i++) z.a[i+1]+=z.a[i]/10,z.a[i]%=10;
return z;
}
void print()
{
int lim=M; while(lim>1&&!a[lim]) lim--;
for(int i=lim;i>=1;i--) printf("%d",a[i]);
printf("\n");
}
}f[N][1<<9],g[N][1<<9];
void dfs(int x,int lim)
{
if(pas[x][lim]) return ; pas[x][lim]=true;
if(x==n+1) return f[x][lim].a[1]=1,void();
for(int i=1;i<=k;i++)
{
if((lim>>i-1)&1) continue;
int p1=x+1,p2=lim|can[i]; dfs(p1,p2);
f[x][lim]=f[x][lim]+f[p1][p2];
g[x][lim]=g[x][lim]+(f[p1][p2]<<(n-x))*i+g[p1][p2];
}
return ;
}
signed main()
{
freopen("hacker2.in","r",stdin); freopen("hacker2.out","w",stdout);
n=read(); m=read(); k=read();
for(int i=1,a,b;i<=m;i++) a=read(),b=read(),can[a]|=1<<(b-1);
dfs(1,0); f[1][0].print(); g[1][0].print();
return 0;
}
PS
尽管这一天是我生日,要是没有 星痕Force_A 的提醒还真就忘了。。