Vjudge 20220429 练习14
written on 2022-05-02
A题数学推导题,但是因为数学太菜所以只会打表找规律。答案的递推公式是 \(a[n]=6a[n-1]-a[n-2]+2\) 。正解是化简以后用佩尔方程找递推式。佩尔方程以后再补,先放一个链接。找到递推式以后,就能用高精模拟求解了
B题是博弈论的题,题目若与博弈有关,那么就要牢牢抓住博弈的两个重要结论
-
能转移到必败态的都是必胜态
-
只能转移到必胜态的就是必败态
观察到n的范围只有7000,于是大胆暴力模拟,用dfs模拟状态然后根据两个结论转移。因为同一个已经搜过的状态再搜一次肯定没必要,所以加上记忆化就能过了
code
#include<bits/stdc++.h>
#define N 7005
using namespace std;
int n,p,q,a[N],b[N];
bool vis[N][2];
int ans[N][2],cnt[N][2];
void dfs(int x,bool sta)
{
if(vis[x][sta]) return ;
vis[x][sta]=1;
if(sta==1)
{
for(int i=1;i<=p;i++)
{
int y=x-a[i];
if(y<1) y+=n;
if(y==1||vis[y][sta^1]) continue;
if(ans[x][sta]==0) ans[y][sta^1]=1,dfs(y,sta^1);
else if(++cnt[y][sta^1]==p) ans[y][sta^1]=0,dfs(y,sta^1);
}
}
else
{
for(int i=1;i<=q;i++)
{
int y=x-b[i];
if(y<1) y+=n;
if(y==1||vis[y][sta^1]) continue;
if(ans[x][sta]==0) ans[y][sta^1]=1,dfs(y,sta^1);
else if(++cnt[y][sta^1]==q) ans[y][sta^1]=0,dfs(y,sta^1);
}
}
}
int main()
{
scanf("%d",&n);
scanf("%d",&p);for(int i=1;i<=p;i++) scanf("%d",&a[i]);
scanf("%d",&q);for(int i=1;i<=q;i++) scanf("%d",&b[i]);
memset(ans,-1,sizeof(ans));
ans[1][1]=0,dfs(1,1),ans[1][0]=0,dfs(1,0);
for(int i=2;i<=n;i++)
{
if(ans[i][0]==1) printf("Win ");
else if(ans[i][0]==0) printf("Lose ");
else printf("Loop ");
}
puts("");
for(int i=2;i<=n;i++)
{
if(ans[i][1]==1) printf("Win ");
else if(ans[i][1]==0) printf("Lose ");
else printf("Loop ");
}
}
先看D题,cpp教诲我们,做这种区间可能发生转移的题目,可以考虑固定左端点,然后再想删除左端点后对后续答案的影响。后续答案显然可以用一个普通线段树来维护,那么现在我们要做的事就有以下几点:
-
预处理出以1为左端点的所有mex,加入线段树,维护区间和
-
找到第一个与该数( \(a[x]\) )相同的数的位置 \(y\) 。
-
将 \(x+1\) ~ \(y-1\) 这些位置mex大于 \(a[x]\) 的值全部用线段树改为 \(a[x]\)
-
统计答案
对于1操作,我的做法是,新开一个线段树,然后二分找到第一个不为0的位置,该位置即为从1开始的mex值
PS:1操作中的线段树值域要开到n+1
code
#include<iostream>
#include<cstdio>
#include<cstring>
#define N 200005
using namespace std;
typedef long long ll;
int n,a[N];
int M[N],to[N];
struct Seg
{
int siz[N<<2];
ll val[N<<2],add[N<<2];
void build(int p,int l,int r)
{
val[p]=siz[p]=0;
add[p]=-1;
if(l==r) return ;
int mid=l+r>>1;
build(p<<1,l,mid),build(p<<1|1,mid+1,r);
}
void update(int p,int l,int r,int x)
{
if(l==r)
{
siz[p]=1;
return ;
}
int mid=l+r>>1;
if(x<=mid) update(p<<1,l,mid,x);
else update(p<<1|1,mid+1,r,x);
siz[p]=siz[p<<1]+siz[p<<1|1];
}
int find(int p,int l,int r)
{
if(l==r) return l;
int mid=l+r>>1,num=siz[p<<1];
if(num!=mid-l+1) return find(p<<1,l,mid);
else return find(p<<1|1,mid+1,r);
}
void spread(int p,int l,int r)
{
if(add[p]==-1) return ;
int mid=l+r>>1;
val[p<<1]=add[p]*(mid-l+1),val[p<<1|1]=add[p]*(r-mid);
add[p<<1]=add[p<<1|1]=add[p];
add[p]=-1;
}
void update(int p,int l,int r,int x,ll v)
{
if(l==r)
{
val[p]+=v;
return ;
}
spread(p,l,r);
int mid=l+r>>1;
if(x<=mid) update(p<<1,l,mid,x,v);
else update(p<<1|1,mid+1,r,x,v);
val[p]=val[p<<1]+val[p<<1|1];
}
void update(int p,int l,int r,int L,int R,ll v)
{
if(L>R) return ;
if(L<=l&&R>=r)
{
val[p]=1ll*(r-l+1)*v;
add[p]=v;
return ;
}
spread(p,l,r);
int mid=l+r>>1;
if(L<=mid) update(p<<1,l,mid,L,R,v);
if(R>mid) update(p<<1|1,mid+1,r,L,R,v);
val[p]=val[p<<1]+val[p<<1|1];
}
ll ask(int p,int l,int r,int L,int R)
{
if(L<=l&&R>=r) return val[p];
spread(p,l,r);
int mid=l+r>>1;
ll res=0;
if(L<=mid) res+=ask(p<<1,l,mid,L,R);
if(R>mid) res+=ask(p<<1|1,mid+1,r,L,R);
return res;
}
}t1,t2;
void prework1()
{
for(int i=1;i<=n;i++)
{
if(a[i]<=n) t1.update(1,1,n+1,a[i]+1);
M[i]=t1.find(1,1,n+1)-1;
t2.update(1,1,n,i,M[i]);
}
// for(int i=1;i<=n;i++) printf("M=%d ",M[i]);printf("\n");
}
int nxt[N];
void prework2()
{
memset(nxt,0,sizeof(nxt));
for(int i=n;i;i--)
{
if(a[i]>n) continue;
to[i]=n+1;
if(nxt[a[i]]) to[i]=nxt[a[i]];
nxt[a[i]]=i;
}
// for(int i=1;i<=n;i++) printf("to=%d\n",to[i]);printf("\n");
}
int get(int l,int r,ll x)
{
int res=-1;
while(l<=r)
{
int mid=l+r>>1;
if(t2.ask(1,1,n,mid,mid)>x) res=mid,r=mid-1;
else l=mid+1;
}
return res;
}
void work()
{
ll ans=0;
// printf("val=%d\n",t2.ask(1,1,n,1));
for(int i=1;i<=n;i++)
{
ans+=t2.ask(1,1,n,i,n);
if(a[i]>n) continue;
int pos=get(i+1,to[i]-1,1ll*a[i]);
if(pos!=-1) t2.update(1,1,n,pos,to[i]-1,1ll*a[i]);
// printf("i=%d ans=%d pos=%d\n",i,ans,pos);
}
printf("%lld\n",ans);
}
int main()
{
while(scanf("%d",&n)!=EOF)
{
if(!n) return 0;
t1.build(1,1,n+1),t2.build(1,1,n);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
prework1();//预处理出以1为左端点的这些区间的mex值
prework2();//预处理出于a[i]相同的下一个位置
//对于mex<=a[i]的点来说,是没有影响的。但是对于大于a[i]的点来说,是有影响的
work();
}
}//一样要注意细节
C题有两种做法。
1. 用主席数暴力预处理出一段区间内不同的数的个数,对于每一次 \(k\) 的查询,二分最远的点来跳,这样的时间复杂度
\(O(nlog²n)\)
- 利用根号分治的思想,因为 \(∑n/k\) , \(k\)值域 \(1\) ~ \(n\) 的值可证明小于nlogn,然后答案又具有二分性,因此暴力向右二分相等的答案,暴力输出即可。题解
第一份代码懒得打了,就贴第二份代码了
#include<bits/stdc++.h>
#define N 100005
using namespace std;
int n,a[N];
bool mark[N];
int ans[N];
int work(int x)
{
if(ans[x]) return ans[x];
int res=0;
for(int i=1;i<=n;i++)
{
int t=i,now=0;
while(t<=n)
{
if(!mark[a[t]])
{
if(now==x)
{
res++,now=0;
break;
}
mark[a[t]]=1;
now++;
}
t++;
}
if(now) res++;
for(int j=t;j>=i;j--) mark[a[j]]=0;
i=t-1;
}
return ans[x]=res;
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
int B=sqrt(n);
for(int i=1;i<=B;i++) printf("%d ",work(i));
for(int i=B+1;i<=n;i++)
{
int now=work(i);
int l=i+1,r=n,res=i;
while(l<=r)
{
int mid=l+r>>1;
if(work(mid)==now) l=mid+1,res=mid;
else r=mid-1;
}
for(int j=i;j<=res;j++) printf("%d ",now);
i=res;
}
}
update on 2022-7-31
注意其中 \(C\) 题的第二种根号分治的做法,这种思想在一些根号分治的题目中还是比较常见的。