2019-2020 XX Opencup GP of Tokyo
链接
F. Robots
题解
I. Amidakuji
题解
H. Construct Points
\(\color{gray}{\texttt{skipped}}\)
E. Count Modulo 2
题意
有 \(n\) 个不同的物体,问完全背包选 \(n\) 个大小为 \(s\) 的方案数 \(\bmod 2\) 的结果。
题解
可以发现 \(n,s\) 都很大,显然不能直接用背包之类的方法做。但是每个物体的大小都很小,考虑从这方面入手。
考虑形式化问题,即 \(\sum p_i=n,\sum p_ia_i=s\) 的方案数。考虑一组 \(p_i\) 的贡献,即 \(\binom{n}{p_1,p_2,p_3\cdots}=\frac{n!}{\prod p_i!}\)。
为了产生贡献上下 \(2\) 的因子个数要相同,又有 \(\sum p_i=n\),故每个 \(p_i\) 的任意位都不能相同。这样可以把 \(n\) 的每一位分配给一个 \(p_i\)。
将 \(n\) 二进制拆分后变成 \(\sum_{i\in n}a_{x_i}=s\)。
考虑从高到低位处理,用 bitset 优化可行的方案,复杂度 \(O(\frac{kV\log n}{\omega})\)。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<bitset>
#define N 200010
#define ll long long
using namespace std;
int a[N];
bitset<N>f,g;
int main()
{
int t;
scanf("%d",&t);
while(t --> 0)
{
ll n,s;int k;
scanf("%lld%lld%d",&n,&s,&k);
int m=0;
for(int i=1;i<=k;i++) scanf("%d",&a[i]),m=max(m,a[i]);
f.reset(),f.set(0);
for(int i=0;i<=60;i++)
{
g.reset();
if(n>>i&1) for(int j=1;j<=k;j++) g^=f<<a[j];
else g=f;
f.reset();
for(int j=0;j<m;j++) f[j]=g[j<<1|(s>>i&1)];
}
printf("%d\n",f.test(0));
}
return 0;
}
L. Yosupo's Algorithm
又是一个 XJ 原题。
考虑将两部分分开做,那么等同于问红点和蓝点一段前缀的答案。
按 \(y\) 坐标分治,假设红区间为 \([l,m]\) 蓝区间为 \([m+1,r]\)。可以证明的是,假如一组答案分别位于两个区间内,那么其中一定包含左区间最大值或是右区间最大值。
然后将 \(O(n\log n)\) 个点用二维数点的方式扫一遍即可,复杂度 \(O(n\log^2 n+q\log n)\)。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#define N 100010
#define M N*20
#define S 1000010
#define inf 1000000000
using namespace std;
struct node{
int x,y,w;
bool operator <(const node a)const{return x==a.x?y<a.y:x<a.x;}
}a[N],b[N],q[S];
int n;
typedef vector<int> vec;
void split(vec &v,vec &vl,vec &vr,int mid,node f[])
{
for(int p:v) if(f[p].y<=mid) vl.push_back(p);
else vr.push_back(p);
}
node g[M];int gt;
void solve(int l,int r,vec &va,vec &vb)
{
if(va.empty() || vb.empty()) return;
int mid=(l+r)>>1;
vec vla,vra,vlb,vrb;
split(va,vla,vra,mid,a);split(vb,vlb,vrb,mid,b);
int ma=0,mb=0;
for(int v:vla) if(a[v].w>a[ma].w) ma=v;
for(int v:vrb) if(b[v].w>b[mb].w) mb=v;
if(ma && mb)
{
for(int v:vrb) g[++gt]=(node){a[ma].x,b[v].x,a[ma].w+b[v].w};
for(int v:vla) g[++gt]=(node){a[v].x,b[mb].x,a[v].w+b[mb].w};
}
solve(l,mid,vla,vlb),solve(mid+1,r,vra,vrb);
}
int py[S],yt;
int px[S],xt;
int val[S];
void adds(int x,int v){for(;x<=xt;x+=(x&(-x))) val[x]=max(val[x],v);}
void addp(int x,int v){for(;x;x-=(x&(-x))) val[x]=max(val[x],v);}
int qrys(int x,int v=-1){for(;x<=xt;x+=(x&(-x))) v=max(v,val[x]);return v;}
int qryp(int x,int v=-1){for(;x;x-=(x&(-x))) v=max(v,val[x]);return v;}
int ans[S];
int main()
{
memset(val,-1,sizeof(val));
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%d%d%d",&a[i].x,&a[i].y,&a[i].w),py[++yt]=a[i].y;
for(int i=1;i<=n;i++) scanf("%d%d%d",&b[i].x,&b[i].y,&b[i].w),py[++yt]=b[i].y;
int m;
scanf("%d",&m);
for(int i=1;i<=m;i++) scanf("%d%d",&q[i].x,&q[i].y),q[i].w=i;
sort(py+1,py+yt+1);
yt=unique(py+1,py+yt+1)-py-1;
for(int i=1;i<=n;i++) a[i].y=lower_bound(py+1,py+yt+1,a[i].y)-py;
for(int i=1;i<=n;i++) b[i].y=lower_bound(py+1,py+yt+1,b[i].y)-py;
for(int i=1;i<=n;i++) px[++xt]=a[i].x;
for(int i=1;i<=m;i++) px[++xt]=q[i].x;
sort(px+1,px+xt+1);
xt=unique(px+1,px+xt+1)-px-1;
for(int i=1;i<=n;i++) a[i].x=lower_bound(px+1,px+xt+1,a[i].x)-px;
for(int i=1;i<=m;i++) q[i].x=lower_bound(px+1,px+xt+1,q[i].x)-px;
xt=0;
for(int i=1;i<=n;i++) px[++xt]=b[i].x;
for(int i=1;i<=m;i++) px[++xt]=q[i].y;
sort(px+1,px+xt+1);
xt=unique(px+1,px+xt+1)-px-1;
for(int i=1;i<=n;i++) b[i].x=lower_bound(px+1,px+xt+1,b[i].x)-px;
for(int i=1;i<=m;i++) q[i].y=lower_bound(px+1,px+xt+1,q[i].y)-px;
vec va,vb;
for(int i=1;i<=n;i++) va.push_back(i),vb.push_back(i);
solve(1,yt,va,vb);
sort(q+1,q+m+1);sort(g+1,g+gt+1);
for(int i=1;i<=m;i++) ans[i]=-1;
// for(int i=1;i<=m;i++) printf("ques : %d %d %d\n",q[i].x,q[i].y,q[i].w);
// for(int i=1;i<=gt;i++) printf("node : %d %d %d\n",g[i].x,g[i].y,q[i].w);
int u=1;
//x<X y>Y
for(int l=1,r=1;l<=gt;l=r)
{
for(;u<=m && q[u].x<g[l].x;u++) ans[q[u].w]=max(ans[q[u].w],qrys(q[u].y));
for(r=l;g[r].x==g[l].x;r++) addp(g[r].y,g[r].w);
}
for(;u<=m;u++) ans[q[u].w]=max(ans[q[u].w],qrys(q[u].y));
u=1;
memset(val,-1,sizeof(val));
reverse(q+1,q+m+1);reverse(g+1,g+gt+1);
//x>X y<Y
for(int l=1,r=1;l<=gt;l=r)
{
for(;u<=m && q[u].x>g[l].x;u++) ans[q[u].w]=max(ans[q[u].w],qryp(q[u].y));
for(r=l;g[r].x==g[l].x;r++) adds(g[r].y,g[r].w);
}
for(;u<=m;u++) ans[q[u].w]=max(ans[q[u].w],qryp(q[u].y));
for(int i=1;i<=m;i++) printf("%d\n",ans[i]);
return 0;
}
/*
2
-3 1 1
-6 3 10
3 4 100
5 2 1000
5
-5 4
-2 6
-4 1
-10 10
-1 2
*/
B. Evacuation
首先如果 \(S\) 个人的位置 \(p\) 满足 \(r-p>p-l\),那么他们一定不会往左跑出 \(l\)。这样可以按照中点 \(m\) 将区间分为 \([l,m],[m+1,r]\) 两段,左边最多只能跑出左边界,右边同理。
令 \(f(p,r)\) 表示 \(S\) 个人在 \(p\),当前限制为 \(r\)(超过 \(r\) 就安全了),可以发现 \(f\) 能够 \(O(1)\) 计算,具体来说预处理距离 \(p\) 满足能承受 \(S\) 个人的最小半径 \(R_p\),并统计 \(i\times a_i\) 的前缀和,如果 \(|r-p|\leq R_p\) 那么多出部分会直接到安全地带,分类讨论一下即可。
然后可以发现的是 \(f(p,r)-f(p,r+1)\geq f(p+1,r)-f(p+1,r+1)\)。正确性挺显然的,其实是我不会证。然后就有了决策单调性,即对于一个区间 \([l,r]\),令 \(f(x,r_i)\) 的最大位置为 \(x_i\),有 \(r_i\leq r_j\Leftrightarrow x_i\leq x_j\)。
直接线段树套整体二分即可。复杂度 \(O(n\log^2 n)\)。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<vector>
#include<algorithm>
#define N 200010
#define ll long long
using namespace std;
ll a[N],s[N],ss[N],S;
int n,len[N];
ll w0(int l,int r){return l*s[l]-l*s[r]-ss[l]+ss[r];}
ll W(int x,int y)
{
if(!len[x]) return 0;
int p=min(abs(x-y),len[x]-1);ll r=S-(s[x+p]-s[x-p-1]);
return w0(x,x-p-1)+w0(x,x+p)+r*(p+1);
}
struct node{
int u,v;
node(int U=0,int V=0):u(U),v(V){}
};
ll ans[N];
bool operator <(const node a,const node b){return a.v<b.v;}
struct Con{
vector<node>f[N<<2];
void insert(int u,int l,int r,int L,int R,node v)
{
if(L>R) return;
if(L<=l && r<=R){f[u].push_back(v);return;}
int mid=(l+r)>>1;
if(L<=mid) insert(u<<1,l,mid,L,R,v);
if(R>mid) insert(u<<1|1,mid+1,r,L,R,v);
}
void solve(vector<node>&g,int L,int R,int l,int r)
{
if(L>R) return;
int mid=(L+R)>>1,id=0;ll mx=-1;
for(int i=l;i<=r;i++)
if(W(i,g[mid].v)>mx) mx=W(i,g[mid].v),id=i;
ans[g[mid].u]=max(ans[g[mid].u],mx);
solve(g,L,mid-1,l,id);solve(g,mid+1,R,id,r);
}
void dfs(int u,int l,int r)
{
sort(f[u].begin(),f[u].end());
solve(f[u],0,(int)f[u].size()-1,l,r);
if(l==r) return;
int mid=(l+r)>>1;
dfs(u<<1,l,mid);dfs(u<<1|1,mid+1,r);
}
}T[2];
int main()
{
scanf("%d%lld",&n,&S);
a[0]=a[n+1]=s[0]=S;
for(int i=1;i<=n;i++) scanf("%lld",&a[i]);
for(int i=1;i<=n+1;i++) s[i]=s[i-1]+a[i],ss[i]=ss[i-1]+a[i]*i;
for(int i=1;i<=n;i++)
{
len[i]=i;
int l=0,r=min(n-i+1,i-1);
while(l<=r)
{
int mid=(l+r)>>1;
if(s[i+mid]-s[i-mid-1]>=S) r=mid-1,len[i]=mid;
else l=mid+1;
}
}
// for(int i=1;i<=n;i++) printf("%d ",len[i]);puts("");
int q;scanf("%d",&q);
for(int i=1;i<=q;i++)
{
int l,r;scanf("%d%d",&l,&r);
int mid=(l+r)>>1;
// for(int j=l;j<=mid;j++) ans[i]=max(ans[i],W(j,l));
// for(int j=mid+1;j<=r;j++) ans[i]=max(ans[i],W(j,r));
T[0].insert(1,1,n,l,mid,node(i,l));
T[1].insert(1,1,n,mid+1,r,node(i,r));
}
T[0].dfs(1,1,n);T[1].dfs(1,1,n);
for(int i=1;i<=q;i++) printf("%lld\n",ans[i]);
return 0;
}
C. Sum Modulo
考虑设第 \(0\text{~}N-1\) 分别为 \(x_0,\cdots,x_{N-1}\) 。可以推出 \(\displaystyle x_i=\sum_{j>0}{\frac {a_j}{S}x_{(i+j)\bmod M}}+1\),并且有 \(x_K=0\)。
前面那个就是常系数非齐次线性递推,由于这题 \(n\) 很小,可以直接绕一圈推出 \(x_i\) 与 \(x_j\) 之间的 \(N-1\) 组关系。
然后递推出 \(x_K\) 与 \(x_0,\cdots,x_{N-1}\) 的关系式,共 \(N\) 组关系就可以高斯消元解出 \(x_0,\cdots,x_{N-1}\)。总复杂度 \(O(N^2\log M+N^3)\)。
代码咕咕咕了
A. Cookies
很妙的题。
首先考虑如果一个 cookie 最后被留下来了,那么开始时再加一个 cookie 它同样还是会被留下来。反过来说,如果一个 cookie 被留下来了,那么开始时就删掉它不会影响其他 cookie 的状态。
这样我们可以看成开始时只有一个 cookie,\(q\) 次给出开始时的 cookie,问最后剩下的 cookie。
题意可以转化为:有一个长度为 \(n\) 的序列 \(a_i\) 和操作 \(b_i\),对于每次询问,一开始有一个数字 \(v\)。从左往右扫,对于位置 \(i\),如果 \(b_i=\texttt{<}\) 且 \(v< a_i\) 那么交换 \(v,a_i\),否则什么都不敢。\(b_i=\texttt{>}\) 同理。每次询问的改变永久有效。
考虑如果某个连续段 \(b_i\) 都是 \(\texttt{>}\),可以发现这一段只有可能弹出最大的那个元素。即维护一个大根堆,我们只需比较 \(v\) 与 \(\text{Max}\) 的大小即可。对于 \(\texttt{<}\) 同理。
这样我们可以把序列变成 \(\text{Max}_1\text{Min}_1\text{Max}_2\text{Min}_2\cdots \text{Max}_t\text{Min}_t\) 这样若干个堆,只需要从左往右对每个堆的堆顶操作即可。但是直接做复杂度仍然不对。
考虑一个看起来只是优化常数的操作:如果相邻堆满足 \(\text{Min}_i\geq \text{Max}_i\),可以发现无论 \(v\) 是多少,\(\text{Min}_i\) 中弹出的数都不会进入到 \(\text{Max}_i\) 中。换句话说两者的位置无关,那么不妨交换这两个堆的位置,这样可以合并相同操作的两个堆 \(\text{Min}_{i-1} \text{Min}_i\)。
这样看起来复杂度还是很假,但其实是正确的。
考虑这样分析:假设当前有 \(L\) 对 \(\text{Min}_i\text{Max}_i\)。由于已经经过上述的操作,所以 \(\text{Min}_i<\text{Max}_i\)。可以发现无论 \(v\) 是多少,经过该组堆之后都一定会发生变化。
考虑分析势能函数,设 \(\phi_i\) 表示 \(\sum_{s\in\text{Min}_i\ \ t\in\text{Max}_i}[s< t]\)。可以发现一次操作过后,\(\phi_i\) 至少 \(-1\)。当 \(\phi_i=0\) 时,该组堆就会被拆开并被合并。
而对于任意时刻,一定有 \(\sum_{i}\phi_i=M\),根据鸽巢原理,经过 \(O(\frac ML)\) 后至少有一半的 \(\phi_i=0\),即 \(T(L)=T(\frac L2)+O(M\log L)\)。分析一下即为 \(O(M\log^2 L)\)。
总复杂度 \(O((N+M\log L)\log L)\)。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#define N 200010
#define ll long long
using namespace std;
typedef priority_queue<int> pq;
int a[N],b[N];
char op[N];
pq q[N];int L[N],R[N],cnt;
void merge(pq &u,pq &v){while(!v.empty()) u.push(v.top()),v.pop();}
void del(int x)
{
merge(q[x],q[R[R[x]]]);
merge(q[R[x]],q[R[R[R[x]]]]);
R[R[x]]=R[R[R[R[x]]]];
L[R[R[x]]]=R[x];
}
void check(int x)
{
for(int _=4,y=x;_;_--,y=R[y]) if(y==R[y]) return;
if(q[x].empty() || q[R[x]].empty()) return;
if(q[R[x]].top()+q[R[R[x]]].top()<=0) del(x);
}
int main()
{
int n,m;
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
scanf("%d",&m);
for(int i=1;i<=m;i++) scanf("%d",&b[i]);
scanf("%s",op+1);
for(int i=1,j=1;i<=m;i=j)
{
for(;j<=m && op[i]==op[j];j++);
if(op[i]=='B')
{
++cnt;
for(int k=i;k<j;k++) q[cnt].push(b[k]);
}
else
{
++cnt;
for(int k=i;k<j;k++) q[cnt].push(-b[k]);
}
}
for(int i=1;i<=cnt;i++) R[i]=i+1,L[i]=i-1;
R[cnt]=cnt;
for(int j=1;j!=R[j];j=R[j])
check(j);
ll las=0;
for(int i=1;i<=n;i++)
{
ll w=(a[i]+las)%1000000000;
for(int j=1;;j=R[j])
{
if(q[j].top()<0 && -q[j].top()<w)
{
ll v=-q[j].top();
// printf("push %d %d\n",v,w);
q[j].pop();q[j].push(-w);
w=v;
}
else if(q[j].top()>0 && q[j].top()>w)
{
ll v=q[j].top();
// printf("push %d %d\n",v,w);
q[j].pop();q[j].push(w);
w=v;
}
if(j==R[j]) break;
}
printf("%lld ",las+=w);
for(int j=1;j!=R[j];j=R[j]) check(j);
}
return 0;
}
J. Median Replace Hard
XJ 原题。
考虑这玩意看起来很难求,但可以发现,任意时刻在末尾加两个字符,多出的选择只有一种:即先合并后面还是合并完前面再合并后面。事实上直接推就能推出一个大概只有不到 100 个状态的自动机。
当然按照 Cyanic 论文里的那个无脑求也是可以的。具体来说如果两个状态是等价的,那么他们后面加上任意字符后都是等价的。不妨钦定一个长度为 10,然后往后依次加长度为 10 的字符串,判断是否等价。
可以发现最后搜出的状态只有十几个。直接在 DFA 上跑 dp,复杂度 \(O(nM+M^3)\),\(M\) 是自动机大小。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<map>
#include<vector>
#include<queue>
#define N 300010
#define mod 1000000007
using namespace std;
const int Len(const int x){return 31-__builtin_clz(x);}
int op;
int calc(int x,int p)
{
int y=x&((1<<p)-1);
if(op&(1<<((x>>p)&7))) y|=1<<p;
return y|(x>>(p+3))<<(p+1);
}
map<int,bool>hv;
bool dfs(int x)
{
int len=Len(x);
if(len%2==0) return false;
if(len==1) return x&1;
if(hv.count(x)) return hv[x];
for(int i=0;i+2<len;i++) if(dfs(calc(x,i))) return hv[x]=true;
return hv[x]=false;
}
bool same(int x,int y,int l)
{
for(int i=0;i<l;i++)
for(int j=0;j<1<<i;j++)
if(dfs(x<<i|j)!=dfs(y<<i|j)) return false;
return true;
}
struct DFA{
struct node{
vector<pair<int,int>>to;
bool las;
};
vector<node> g;
}d;
void init(int l=10)
{
vector<int>V;
queue<int>q;q.push(1);
map<int,int>id;
while(!q.empty())
{
int u=q.front();q.pop();
bool sm=false;
for(int v:V) if(same(u,v,l)){id[u]=id[v];sm=true;break;}
if(sm) continue;
id[u]=V.size();
V.push_back(u);
q.push(u<<1),q.push(u<<1|1);
}
int n=V.size();
d.g.resize(n);
for(int i=0;i<n;i++)
{
d.g[i].to.push_back({0,id[V[i]<<1]});
d.g[i].to.push_back({1,id[V[i]<<1|1]});
if(dfs(V[i])) d.g[i].las=true;
}
}
char s[8],t[N];
void solve()
{
scanf("%s%s",s,t+1);
int n=strlen(t+1);op=0;
for(int i=0;i<8;i++) op|=(s[i]-'0')<<i;
init();
int m=d.g.size();
vector<int> f(m);
f[0]=1;
for(int _=n;_;_--)
{
vector<int>g(m);
char c=t[_];
for(int i=0;i<m;i++)
for(auto v:d.g[i].to)
if(!((c=='0' && v.first==1) || (c=='1' && v.first==0))) g[v.second]=(g[v.second]+f[i])%mod;
f=g;
}
int ans=0;
for(int i=0;i<m;i++) if(d.g[i].las) ans=(ans+f[i])%mod;
printf("%d\n",ans);
}
void clear(){d=DFA();hv.clear();op=0;}
int main()
{
int t;
scanf("%d",&t);
while(t --> 0) solve(),clear();
return 0;
}

浙公网安备 33010602011771号