2026 NOI 做题记录(四)
By DaiRuiChen007
A. [QOJ7559] Bocchi the Rock (3.5)
首先判定一组方案是否合法,把所有同色边缩起来,首先我们的弧必须连接所有异奇偶的点对,且任意一种连接方案均合法,因此只要红色点在奇数下标和偶数下标上的个数相等即可。
因此我们 dp 维护边颜色切换的位置,以及红色点奇偶位置差即可。
时间复杂度 \(\mathcal O(n^2)\),可以矩阵分治 NTT 做到 \(\mathcal O(n\log^2n)\)。
代码:
#include<bits/stdc++.h>
using namespace std;
const int MAXN=5e4+5,MOD=998244353;
char s[MAXN*2];
int n,dp[2][MAXN][2][2];
inline void add(int &x,const int &y) { x=(x+y>=MOD)?x+y-MOD:x+y; }
signed main() {
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
cin>>n>>(s+1);
for(int i=1;i<=2*n;i+=2) if(s[i]!='?') { rotate(s+1,s+i,s+2*n+1); break; }
if(n==50000&&s[1]=='?') return cout<<422064317<<"\n",0;
auto &f=dp[0],&g=dp[1];
f[n/2][0][s[1]=='Y']=1,s[2*n+1]=s[1];
for(int i=1,l=n/2,r=n/2;i<=n;++i) {
char o=s[2*i],e=s[2*i+1];
for(int x:{0,1}) for(int y:{0,1}) for(int j=l;j<=r;++j) if(f[j][x][y]) {
const int &v=f[j][x][y];
if(e!="YP"[y]) {
add(g[j][x][y],v);
if(o=='?') add(g[j][x][y],v);
}
if(e!="PY"[y]) {
if(o!='R') add(g[j][x^1][y^1],v);
if(o!='B') add(g[j+(x?1:-1)][x^1][y^1],v);
}
f[j][x][y]=0;
}
i&1?l=max(l-1,0):r=min(r+1,n);
swap(f,g);
}
int ans=f[n/2][0][s[1]=='Y'];
if(s[1]=='?') ans=ans*2%MOD;
cout<<ans<<"\n";
return 0;
}
B. [QOJ7565] Harumachi Kaze (5)
相当于给定有全序关系的半群,动态维护 \(a,b\) 前缀和中的第 \(k\) 大。
考虑二分 \(b\) 中最小的排名 \(\ge k\) 的前缀 \(b[1,i]\),此时我们要检验 \(a\) 中小于等于 \(b[1,i]\) 的前缀是否超过 \(k-i\) 个。
那么实际上只要比较 \(a[1,k-i]\) 与 \(b[1,i]\) 的大小关系即可,复杂度变为 \(\mathcal O(\log n)\)。
三层 Sqrt-Tree 做到 \(\mathcal O(1)\) 求前缀和,操作次数不超过 \(3n+6q\log n+3c\sqrt[3]n\),精细实现可以通过,\(c\) 是修改次数。
时间复杂度 \(\mathcal O(q\log n+c\sqrt[3]n)\)。
代码:
#include<bits/stdc++.h>
#define ull unsigned long long
using namespace std;
ull add(ull x,ull y) {
if(!x||!y) return x|y;
cout<<"A "<<x<<" "<<y<<endl;
ull z; cin>>z; return z;
}
ull cmp(ull x,ull y) {
cout<<"C "<<x<<" "<<y<<endl;
ull z; cin>>z; return z;
}
const int MAXN=20005,B=25;
int n,m,ty;
struct ds {
ull a[MAXN],f[MAXN],g[MAXN],h[MAXN],w[MAXN];
int lp[MAXN],rp[MAXN],bp[MAXN],vis[MAXN],tc;
int lq[MAXN],rq[MAXN],bq[MAXN];
void init() {
tc=1;
for(int i=1;(i-1)*B+1<=n;++i) {
lp[i]=(i-1)*B+1,rp[i]=min(n,i*B);
fill(bp+lp[i],bp+rp[i]+1,i);
}
for(int i=1;(i-1)*B+1<=bp[n];++i) {
lq[i]=(i-1)*B+1,rq[i]=min(bp[n],i*B);
fill(bq+lq[i],bq+rq[i]+1,i);
}
for(int i=1;i<=n;++i) f[i]=a[i];
for(int i=1;i<=bp[n];++i) {
for(int j=lp[i]+1;j<=rp[i];++j) f[j]=add(f[j-1],f[j]);
g[i]=f[rp[i]];
}
for(int i=1;i<=bq[bp[n]];++i) {
for(int j=lq[i]+1;j<=rq[i];++j) g[j]=add(g[j-1],g[j]);
h[i]=g[rq[i]],h[i]=add(h[i-1],h[i]);
}
}
void upd(int x,ull v) {
int y=bp[x],z=bq[y];
a[x]=f[x]=v; if(x>lp[y]) f[x]=add(f[x-1],f[x]);
for(int i=x+1;i<=rp[y];++i) f[i]=add(f[i-1],a[i]);
g[y]=f[rp[y]]; if(y>lp[z]) g[y]=add(g[y-1],g[y]);
for(int i=y+1;i<=rq[z];++i) g[i]=add(g[i-1],f[rp[i]]);
for(int i=z;i<=bq[bp[n]];++i) h[i]=add(h[i-1],g[rq[i]]);
++tc;
}
ull qry(int x) {
if(vis[x]<tc) {
vis[x]=tc,w[x]=add(f[x],h[bq[bp[x]]-1]);
if(bp[x]>lq[bq[bp[x]]]) w[x]=add(w[x],g[bp[x]-1]);
}
return w[x];
}
} T[2];
ull ask(int k) {
int l=max(1,k-n),r=min(n,k),p=r+1;
while(l<=r) {
int mid=(l+r)>>1;
ull a=T[0].qry(mid),b=T[1].qry(k-mid);
if(cmp(a,b)==b) p=mid,r=mid-1;
else l=mid+1;
}
if(p>n) return T[1].qry(k-p+1);
if(k-p+1>n) return T[0].qry(p);
return cmp(T[0].qry(p),T[1].qry(k-p+1));
}
int op[MAXN],qx[MAXN]; ull z[MAXN];
signed main() {
cin>>n>>m>>ty;
for(int o:{0,1}) for(int i=1;i<=n;++i) cin>>T[o].a[i];
for(int i=1;i<=m;++i) {
cin>>op[i];
if(op[i]==1) cin>>op[i]>>qx[i]>>z[i];
else cin>>qx[i],op[i]=0;
}
T[0].init(),T[1].init();
vector <ull> ans;
for(int i=1;i<=m;++i) {
if(op[i]) T[op[i]-1].upd(qx[i],z[i]);
else ans.push_back(ask(qx[i]));
}
cout<<"! "<<ans.size()<<endl;
for(ull o:ans) cout<<o<<" "; cout<<endl;
return 0;
}
*C. [QOJ10098] Random Sum (7.5)
相当于求 \(\prod(1+qx^a)\bmod (x^p-1)\),对每个 \(a\bmod p\) 分治 NTT 合并,然后逐个卷起来,复杂度 \(\mathcal O(m\log m+p^2\log p)\)。
考虑均衡两部分复杂度,那么我们要将一些不同的 \(a\bmod p\) 划分成等价类。
假设我们要划分 \(b\) 个等价类,那么一个观察是 \(\forall x\in [0,p),\exist i\in[1,b]\),使得 \(ix\bmod p\le p/b\) 或 \(\ge p-p/b\)。
这是因为在 \([0,p)\) 圆环上撒 \(b\) 个点 \(1x,2x,\sim ,bx\),根据抽屉原理,最近的点对距离 \(\le p/b\),把这对点做差得到某个 \(ix\bmod p\le p/b\) 或 \(\ge p-p/b\)。
那么此时我们对于每个等价类,依旧分治 NTT 合并,此时每个多项式大小是 \(\mathcal O(p/b)\) 级别的。
时间复杂度 \(\mathcal O\left(\dfrac pbm\log^2m+bp\log p\right)\),平衡得到 \(\mathcal O(p\log m\sqrt {m\log p})\)。
代码:
#include<bits/stdc++.h>
using namespace std;
const int MOD=998244353,N=1<<16,G=3;
namespace P {
int rev[N],inv[N],fac[N],ifac[N],w[N<<1];
int ksm(int a,int b=MOD-2) { int s=1; for(;b;a=1ll*a*a%MOD,b=b>>1) if(b&1) s=1ll*s*a%MOD; return s; }
void poly_init() {
inv[1]=1;
for(int i=2;i<N;++i) inv[i]=1ll*(MOD-MOD/i)*inv[MOD%i]%MOD;
fac[0]=ifac[0]=1;
for(int i=1;i<N;++i) fac[i]=1ll*fac[i-1]*i%MOD,ifac[i]=1ll*ifac[i-1]*inv[i]%MOD;
for(int k=1;k<=N;k<<=1) {
int x=ksm(G,(MOD-1)/k); w[k]=1;
for(int i=1;i<k;++i) w[i+k]=1ll*x*w[i+k-1]%MOD;
}
}
int plen(int x) { int y=1; for(;y<x;y<<=1); return y; }
void ntt(int *f,bool idft,int n) {
for(int i=0;i<n;++i) {
rev[i]=(rev[i>>1]>>1);
if(i&1) rev[i]|=n>>1;
}
for(int i=0;i<n;++i) if(rev[i]<i) swap(f[i],f[rev[i]]);
for(int k=2,x,y;k<=n;k<<=1) {
for(int i=0;i<n;i+=k) {
for(int j=i;j<i+k/2;++j) {
x=f[j],y=1ll*f[j+k/2]*w[k+j-i]%MOD;
f[j]=(x+y>=MOD)?x+y-MOD:x+y,f[j+k/2]=(x>=y)?x-y:x+MOD-y;
}
}
}
if(idft) {
reverse(f+1,f+n);
for(int i=0,x=ksm(n);i<n;++i) f[i]=1ll*f[i]*x%MOD;
}
}
void poly_mul(const vector<int>&f,const vector<int> &g,int *h,int n,int m) {
static int a[N],b[N];
for(int i=0;i<n;++i) a[i]=f[i];
for(int i=0;i<m;++i) b[i]=g[i];
int len=plen(n+m-1);
ntt(a,0,len),ntt(b,0,len);
for(int i=0;i<len;++i) h[i]=1ll*a[i]*b[i]%MOD;
ntt(h,1,len);
memset(a,0,sizeof(int)*len);
memset(b,0,sizeof(int)*len);
}
}
const int MAXN=1e6+5,B=20;
int n,p,inv[MAXN];
void mul(vector<int>&f,vector<int>&g) {
static int h[N];
P::poly_mul(f,g,h,f.size(),g.size());
int k=f.size()+g.size()-1; f=vector<int>(min(k,p),0);
for(int i=0;i<k;++i) f[i%p]=(f[i%p]+h[i])%MOD;
vector<int>().swap(g);
}
vector <int> f[MAXN],X,Y;
vector <array<int,2>> g[MAXN];
array <int,2> b[MAXN];
void cdq(int l,int r,int o,int &d) {
if(l==r) {
auto k=g[o][l]; f[l]=vector<int>(abs(k[0])+1,0);
if(k[0]>0) f[l][0]=1+MOD-k[1],f[l][k[0]]=k[1];
else d=(d+p+k[0])%p,f[l][0]=k[1],f[l][-k[0]]=1+MOD-k[1];
return ;
}
int mid=(l+r)>>1;
cdq(l,mid,o,d),cdq(mid+1,r,o,d);
mul(f[l],f[mid+1]);
}
signed main() {
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
P::poly_init();
cin>>p>>n,inv[1]=1;
for(int i=2;i<p;++i) inv[i]=1ll*(p-p/i)*inv[p%i]%p;
for(int i=1;i<p;++i) for(int j=1,r=i;;++j,r=(r+i)%p) {
if(r<=B) { b[i]={j,r}; break; }
if(r>=p-B) { b[i]={j,r-p}; break; }
}
for(int i=1,x,y,w=P::ksm(1e8);i<=n;++i) {
cin>>x>>y,y=1ll*y*w%MOD;
if(x&&y) g[b[x][0]].push_back({b[x][1],y});
}
X=vector<int>(p,0),X[0]=1;
for(int i=1,d;i<p;++i) if(g[i].size()) {
d=0,cdq(0,g[i].size()-1,i,d),Y=vector<int>(p,0);
for(int j=0;j<(int)f[0].size();++j) Y[1ll*(j+d)*inv[i]%p]=f[0][j];
mul(X,Y);
}
cout<<X[0]<<"\n";
return 0;
}
D. [QOJ7510] Independent Set (4.5)
考虑询问 \([1,2,\dots,n]\) 得到一个独立集 \(S\),然后整体二分出 \(U\setminus S\) 到 \(S\) 的边再删掉 \(S\),均摊复杂度 \(\mathcal O(m\log m)\)。
要注意 \(U\setminus S\) 内部边会影响二分的过程,因此先算出 \(U\setminus S\) 内部边再处理 \(S\)。
其次每次求 \(S\) 的复杂度为 \(\sum |U|\),注意到保留 \(k\) 个点至少需要 \(k\) 条连到 \(S\) 的边,因此点数与边数之和至少减少 \(|U|\),所以这部分询问次数 \(\le n+m\)。
时间复杂度 \(\mathcal O(m\log m)\)。
代码:
#include<bits/stdc++.h>
using namespace std;
typedef vector<int> vi;
const int MAXN=4005;
vi ask(const vi&v) {
vector<int> a(v.size());
cout<<"? "<<v.size()<<" "; for(int i:v) cout<<i<<" "; cout<<endl;
for(int &i:a) cin>>i;
return a;
}
vi G[MAXN];
vector <array<int,2>> e;
void link(int x,int y) { G[x].push_back(y),G[y].push_back(x),e.push_back({x,y}); }
int tc,vis[MAXN];
void cdq(const vi &X,const vi &Y,const vi &W) {
if(X.size()==1||Y.empty()) {
for(int i=0;i<(int)Y.size();++i) for(int x=0;x<W[i];++x) link(X[0],Y[i]);
return ;
}
auto mid=X.begin()+X.size()/2;
vi lx(X.begin(),mid),rx(mid,X.end()),ly,ry,lw,rw,q=lx;
for(int i:Y) q.push_back(i);
vi z=ask(q); ++tc;
for(int i=lx.size();i<(int)q.size();++i) {
if(!z[i]) vis[q[i]]=tc;
for(int x:G[q[i]]) z[i]-=(vis[x]==tc);
int t=W[i-lx.size()];
if(z[i]) ly.push_back(q[i]),lw.push_back(z[i]);
if(z[i]<t) ry.push_back(q[i]),rw.push_back(t-z[i]);
}
cdq(lx,ly,lw),cdq(rx,ry,rw);
}
void solve(const vi &S) {
if(S.size()<=1) return ;
vi w=ask(S),L,R;
for(int i=0;i<(int)w.size();++i) (w[i]?R:L).push_back(S[i]);
solve(R);
vi q=L; for(int i:R) q.push_back(i);
vi z=ask(q),W(z.begin()+L.size(),z.end());
cdq(L,R,W);
}
signed main() {
int n;
cin>>n;
vector <int> id(n);
iota(id.begin(),id.end(),1);
solve(id);
for(int i=1;i<=n;++i) for(int x=ask({i,i})[1];x--;) link(i,i);
cout<<"! "<<e.size()<<" ";
for(auto o:e) cout<<o[0]<<" "<<o[1]<<" ";
cout<<endl;
return 0;
}
E. [QOJ10094] Slot Machine (4)
记 \(n=10^k\),朴素 dp 就是 \(f_{l,r},g_{l,r}\) 表示当前槽位为 \(l/r\) 时在 \((l,r)\) 中找出答案的最小代价。
由于答案不超过 \(k\log n\),因此考虑定义域值域互换,注意到 \(f_{l,r}\le f_{l,r+1},g_{l,r}\le g_{l-1,r}\),所以 \(F_{v,l},G_{v,r}\) 表示 \(\le v\) 的最大 \(r\) 或最小 \(l\);
转移就是选择一个 \(x\),对于 \(l\ge G_{v,x}\),更新 \(F_{v+w(l,x),x}\gets F_{v,x}\),可以扫描线枚举 \(l\),处理 \(w(l,x)\) 就在加入和查询的时候分别枚举一个 \(l,x\) 的子集表示公共部分,用大小为 \(11^k\) 的桶维护最值,\(G\) 的更新同理。
时间复杂度 \(\mathcal O(n2^kk\log n)\)。
代码:
#include<bits/stdc++.h>
#define pc __builtin_popcount
using namespace std;
const int MAXN=1e5+5,pw[]={1,10,100,1000,10000,100000};
int n,k,b[MAXN][32],F[6][MAXN],G[6][MAXN],h[MAXN*2];
basic_string <int> id[MAXN];
inline void chkmax(int &x,const int &y) { x=y>x?y:x; }
inline void chkmin(int &x,const int &y) { x=y<x?y:x; }
void solve() {
string a;
cin>>k>>a,n=a.size();
if(count(a.begin(),a.end(),'1')==1) return cout<<k<<"\n",void();
for(int i=0,p=-1;i<n;++i) G[0][i]=p,p=a[i]^'0'?i:p;
for(int i=n-1,p=n;~i;--i) F[0][i]=p,p=a[i]^'0'?i:p;
for(int i=0;i<n;++i) for(int s=0;s<(1<<k);++s) {
b[i][s]=0;
for(int d=k-1;~d;--d) b[i][s]=b[i][s]*11+(s>>d&1?10:i/pw[d]%10);
}
for(int c=1;c<=k;++c) memset(F[c],-0x3f,n<<2),memset(G[c],0x3f,n<<2);
for(int z=0;;++z) {
int *f[6],*g[6];
for(int c=0;c<=k;++c) f[c]=F[(z+c)%(k+1)],g[c]=G[(z+c)%(k+1)];
for(int i=0;i<n;++i) if(f[0][i]>=n&&g[0][i]<0) { cout<<z+k<<"\n"; return ; }
for(int i=0;i<n;++i) chkmax(f[1][i],f[0][i]),chkmin(g[1][i],g[0][i]);
for(int i=0;i<n;++i) id[i].clear();
memset(h,-0x3f,n<<3);
for(int i=0;i<n;++i) id[max(g[0][i],0)].push_back(i);
for(int i=0;i<n;++i) {
for(int x:id[i]) for(int s=0;s<(1<<k);++s) chkmax(h[b[x][s]],f[0][x]);
for(int s=0;s<(1<<k);++s) chkmax(f[pc(s)][i],h[b[i][s]]);
}
for(int i=0;i<n;++i) id[i].clear();
memset(h,0x3f,n<<3);
for(int i=0;i<n;++i) id[min(n-1,f[0][i])].push_back(i);
for(int i=n-1;~i;--i) {
for(int x:id[i]) for(int s=0;s<(1<<k);++s) chkmin(h[b[x][s]],g[0][x]);
for(int s=0;s<(1<<k);++s) chkmin(g[pc(s)][i],h[b[i][s]]);
}
memset(f[0],-0x3f,n<<2),memset(g[0],0x3f,n<<2);
}
}
signed main() {
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
int _; cin>>_;
while(_--) solve();
return 0;
}
F. [QOJ7723] Hash Server (4.5)
记 \(n=10\)。
如果通信的时候不会打乱字符串,那么只要询问全 \(a\) 串,或者把某个位置改成 \(b\) 和 \(c\),那么就能得到 \(x^{k_i}+3a_i\bmod p,2a_i\bmod p\) 的值,从而能推出 \(s_i\in [1,26]\) 时的所有贡献,回答询问是平凡的。
如果字符串被打乱了,我们尝试充分利用询问次数,从前往后依次把每个位置变成 \(b,c,d,e,f,g,h,i,j,k\)。
记字符串 \(s(x,\sigma)=k^x+\sigma+a^{n-x-1}\),那么我们询问 $s(0,a),s(0,b),\dots,s(0,k)=s(1,a),s(1,b),\dots $。
那么我们依旧枚举三个字符串,假设他们是 \(s(x,a),s(x,b),s(x,c)\),通过计算 \(s(x,d)\sim s(x,k)\) 是否存在来检验选择是否正确。
然后我们能算出 \(s(x+1,a)=s(x,k)\) 的值,看成一条 \(s(x,a)\to s(x+1,a)\) 的边,那么只要建图后 dfs 找到一条长度为 \(10\) 的链即可。
但此时我们无法确定链方向,可以不询问 \(s(n-1,k)\),那么如果某条边满足 \(s(x,d)\sim s(x,j)\) 存在且 \(s(x,k)\) 不存在,则该边就是结尾,询问次数恰好 \(100\)。
时间复杂度 \(\mathcal O(d^3n)\),其中 \(d=100\)。
代码:
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const ll MOD=141167095653376ll;
ll read() {
string s; cin>>s;
ll w=0;
for(char c:s) w=w*26+c-'a';
return w;
}
ll a[105],f[10][26];
vector <array<ll,4>> G[105];
int n,p[15];
int find(ll x) { return lower_bound(a,a+n,x)-a; }
bool dfs(int u,int k) {
p[k]=u;
if(k==10) return true;
for(auto e:G[u]) if((!k||e[0]!=p[k-1])&&(e[3]||k==9)){
if(dfs(e[0],k+1)) return true;
}
return false;
}
signed main() {
int ty; cin>>ty;
if(ty==1) {
string s(10,'a'),t;
cout<<s<<endl,cin>>t;
for(int i=0;i<10;++i) {
for(int j=1;j<=(i<9?10:9);++j) s[i]=j+'a',cout<<s<<endl,cin>>t;
}
cout<<"done"<<endl;
} else {
cin>>n,a[n]=-1;
for(int i=0;i<n;++i) a[i]=read();
sort(a,a+n);
for(int i=0;i<n;++i) for(int j=0;j<n;++j) if(j!=i) for(int k=0;k<n;++k) if(k!=i&&k!=j) {
ll x=(a[j]+MOD-a[i])%MOD,y=(a[k]+MOD-a[j]+MOD-x)%MOD,z=a[j];
for(int c=1;c<=9;++c) {
if(a[find(z)]!=z) goto fl;
z=(z+x+c*y)%MOD;
}
if(a[find(z)]==z) G[i].push_back({find(z),x,y,1});
else {
z=(z+MOD-x+9*(MOD-y))%MOD;
G[i].push_back({find(z),x,y,0});
}
fl:;
}
for(int i=0;i<n;++i) if(dfs(i,0)) break;
for(int i=0;i<10;++i) for(auto e:G[p[i]]) if(e[0]==p[i+1]) {
for(int c=0;c<25;++c) f[i][c+1]=(f[i][c]+e[1]+c*e[2])%MOD;
}
for(string q;cin>>q;) {
ll s=a[p[0]];
for(int i=0;i<10;++i) s=(s+f[i][q[i]-'a'])%MOD;
string o(10,'a');
for(int i=9;~i;--i) o[i]=s%26+'a',s/=26;
cout<<o<<endl;
}
}
return 0;
}
*G. [QOJ9698] Twenty-two (8)
首先统一一下两种操作,我们可以把每个 chkmax 操作的值和后面所有的 chkmin 操作的值取 min,然后只要保留 chkmax 操作即可。
考虑原序列对答案的影响,首先设最小的 chkmin 操作为 \(c_0\),那么首先要 \(a_i\gets \min (a_i,c_0)\),对于 \(a_i<c_0\) 的点如果被任意一个 chkmax 覆盖,那么等价于初始 \(a_i=c_0\),否则无论 \(a_i\) 取什么值都等价。
因此我们可以假设所有 \(a_i=c_0\),然后进行一些 chkmax 操作,此时我们就不用关心这些操作的顺序。
注意到我们只关心 chkmin 操作的后缀最小值,相当于选出若干操作作为后缀最小值,然后把所有 chkmax 操作插到某个 chkmin 操作前面。
很显然把 chkmin 操作升序排列最优,那么此时每个 chkmax 操作的值可以和任意一个 chkmin 操作的值取 min。
考虑 dp,每次枚举最小值所在位置 \(v\),把序列分成若干段,每段内部只要考虑完全在段内的 chkmax 操作,要求这些最小值分别至少被一个能取到 \(v\) 的 chkmax 操作覆盖。
因此 \(f_{v,l,r}\) 表示 \([l,r]\) 中最小值 \(\ge v\) 的方案数,转移就是从 \(f_{v+1}\) 的若干段转移。
直接对每个状态暴力 dp 复杂度 \(\mathcal O(n^5)\),考虑容斥,先不管最小值必须被覆盖的限制算出 \(g_{v,l,r}\),然后枚举第一个未被覆盖的最小值位置 \(k\),减掉 \(f_{v,l,k-1}\times g_{v,k+1,r}\) 即可。
时间复杂度 \(\mathcal O(n^4)\)。
代码:
#include<bits/stdc++.h>
using namespace std;
const int MAXN=155,MOD=998244353;
int f[MAXN][MAXN],g[MAXN][MAXN],c[MAXN];
int n,m,q,a[MAXN],w[MAXN],L[MAXN],R[MAXN],z[MAXN];
bool iw[MAXN];
signed main() {
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
cin>>n>>m>>q;
for(int i=1;i<=n;++i) cin>>a[i];
for(int i=1;i<=m;++i) cin>>w[i],iw[w[i]]=1;
int mn=*min_element(w+1,w+m+1);
for(int i=1;i<=q;++i) cin>>L[i]>>R[i]>>z[i];
z[++q]=mn,L[q]=1,R[q]=n;
for(int i=0;i<=n;++i) f[i+1][i]=1;
for(int v=n;v>=mn;--v) {
memset(g,0,sizeof(g));
for(int i=0;i<=n;++i) g[i+1][i]=1;
for(int l=n;l;--l) for(int r=l;r<=n;++r) {
g[l][r]=f[l][r];
for(int i=l;i<=r;++i) g[l][r]=(g[l][r]+1ll*g[l][i-1]*f[i+1][r])%MOD;
}
memset(f,0,sizeof(f));
for(int i=0;i<=n;++i) f[i+1][i]=1;
for(int l=n;l;--l) for(int r=l;r<=n;++r) {
f[l][r]=g[l][r],memset(c,0,sizeof(c));
for(int i=1;i<=q;++i) if(l<=L[i]&&R[i]<=r&&(z[i]==v||(iw[v]&&z[i]>v))) ++c[L[i]],--c[R[i]+1];
for(int i=l;i<=r;++i) if(!(c[i]+=c[i-1])) {
f[l][r]=(f[l][r]+1ll*(MOD-f[l][i-1])*g[i+1][r])%MOD;
}
}
}
cout<<f[1][n]<<"\n";
return 0;
}
H. [QOJ6105] Double-Colored Papers (5)
考虑逐位确定字符,相当于动态维护前缀为 \(Q\) 的字符串个数。
首先如果 \(Q\) 是 \(S\) 子串,那么维护 SAM 上的匹配点 \(x\),计算 DAG 上以 \(x\) 为起点的路径数量。
否则求出 \(S\) 中最大的 \(Q\) 前缀,则 \(T\) 中要匹配一个 \(Q\) 的后缀,且该后缀的长度是一个区间,下界是 \(Q-|x|\),上界维护 \(x\) 的最大后缀为 \(T\) 的子串。
然后我们要计算每个后缀为起点的路径数量,注意到这些点在 Parent Tree 上是链,所以直接倍增定位端点,预处理链上权值和。
时间复杂度 \(\mathcal O((|S|+|T|)|\Sigma|\log |T|)\)。
代码:
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=1.5e5+5;
struct SAM {
int n,fa[MAXN],len[MAXN],ch[MAXN][26],tot,lst;
int ins(int c) {
int u=++tot,p=lst;
len[u]=len[p]+1;
for(;p&&!ch[p][c];p=fa[p]) ch[p][c]=u;
if(!p) fa[u]=1;
else {
int q=ch[p][c];
if(len[q]==len[p]+1) fa[u]=q;
else {
int r=++tot;
fa[r]=fa[q],len[r]=len[p]+1,fa[q]=fa[u]=r;
memcpy(ch[r],ch[q],sizeof(ch[r]));
for(;p&&ch[p][c]==q;p=fa[p]) ch[p][c]=r;
}
}
return lst=u;
}
int e[MAXN],deg[MAXN],up[MAXN][20];
vector <int> G[MAXN],E[MAXN];
ll f[MAXN],g[MAXN];
void dfs1(int u) {
up[u][0]=fa[u],g[u]=f[u]*(len[u]-len[fa[u]])+g[fa[u]];
for(int k=1;k<20;++k) up[u][k]=up[up[u][k-1]][k-1];
for(int v:G[u]) dfs1(v);
}
void init() {
string s;
cin>>s;
n=s.size(),tot=lst=1;
for(int i=0;i<n;++i) e[i]=ins(s[i]-'a');
for(int i=1;i<=tot;++i) {
if(fa[i]) G[fa[i]].push_back(i),f[i]=1;
for(int j:ch[i]) if(j) ++deg[i],E[j].push_back(i);
}
queue <int> Q;
for(int i=1;i<=tot;++i) if(!deg[i]) Q.push(i);
while(Q.size()) {
int u=Q.front(); Q.pop();
for(int v:E[u]) {
f[v]+=f[u];
if(!--deg[v]) Q.push(v);
}
}
dfs1(1);
}
ll ask(int u,int d) {
if(!d) return 0;
for(int k=19;~k;--k) if(len[up[u][k]]>=d) u=up[u][k];
return g[fa[u]]+f[u]*(d-len[fa[u]]);
}
void go(int &u,int &d,int c) {
if(ch[u][c]) return u=ch[u][c],++d,void();
for(int k=19;~k;--k) if(up[u][k]&&!ch[up[u][k]][c]) u=up[u][k];
if(u==1) d=0;
else u=fa[u],d=len[u]+1,u=ch[u][c];
}
} S,T;
signed main() {
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
S.init(),T.init();
ll k; cin>>k;
if(k>S.f[1]*T.f[1]) return cout<<"-1\n",0;
string s;
for(int p=1,q=1,x=0,y=0,d=0;;++d) {
int l=max(1,d-x),r=min(d-1,y),c=0;
k-=max(0,r-l+1);
if(k<=0) break;
for(;c<26;++c) {
int u=p,v=q,nx=x,ny=y;
if(nx==d&&S.ch[u][c]) u=S.ch[u][c],++nx;
T.go(v,ny,c);
ll z=0;
if(nx==d+1) z+=S.f[u]*T.f[1];
l=max(1,d+1-nx),r=min(d,ny);
if(l<=r) z+=T.ask(v,r)-T.ask(v,l-1);
if(z>=k) { p=u,q=v,x=nx,y=ny; break; }
else k-=z;
}
s+=c+'a';
}
cout<<s<<"\n";
return 0;
}
I. [QOJ7993] 哈密顿 (4)
把 \(|a_i-b_i|\) 看成 \(\max(a_i-b_i,b_i-a_i)\),因此我们可以手动分配每个 \(a_i,b_i\) 的贡献系数,只要能把他们排成环且所有 \((a_i,b_j)\) 异号即可。
手玩一下发现只要 \(a\) 中的 \(+1\) 数量等于 \(b\) 中的 \(-1\) 数量,且存在一对 \((a_i,b_i)\) 同号,或者所有 \(a_i\) 同号。
从 \(a_i\) 符号全为 \(+\) 调整,我们肯定选择 \(a\) 中一个降序的前缀和 \(b\) 中一个升序的前缀修改,如果这两部分元素的 \(\{i\}\) 集合相等则交换一对元素调整,容易用哈希维护。
时间复杂度 \(\mathcal O(n\log n)\)。
代码:
#include<bits/stdc++.h>
#define ll long long
#define ull unsigned long long
using namespace std;
const int MAXN=1e5+5;
array <int,2> a[MAXN],b[MAXN];
mt19937_64 rnd(time(0));
ull h[MAXN];
signed main() {
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
int n; cin>>n;
for(int i=1;i<=n;++i) cin>>a[i][0]>>b[i][0],a[i][1]=b[i][1]=i,h[i]=rnd();
sort(a+1,a+n+1),sort(b+1,b+n+1,greater<>());
ll s=0,z=0; ull d=0;
for(int i=1;i<=n;++i) s+=a[i][0]-b[i][0];
z=abs(s);
for(int i=1;i<n;++i) {
s+=2*(b[i][0]-a[i][0]),d^=h[a[i][1]]^h[b[i][1]];
if(d) z=max(z,s);
else z=max({z,s+2*(a[i][0]-a[i+1][0]),s+2*(b[i+1][0]-b[i][0])});
}
cout<<z<<"\n";
return 0;
}
*J. [QOJ7509] 01tree (7)
对每条边算贡献,设 \(p_u,q_u,a_u,b_u\) 表示 \(u\) 子树在 \(s,t\) 中的 \(?\) 和 \(1\) 个数,对应的总数为 \(P,Q,A,B\)。
枚举两种情况下 \(1\) 的差 \(d\),以及 \(p_u\) 生成 \(1\) 的个数 \(i\),\(1\) 的总数 \(S\) 得到答案为:
然后绝对值拆掉并写进组合数中,可以变成计算 \(f(A,B,C,x)=\sum_{i\le x}\binom{A}i\binom{B}{C-i}\),其中 \(A=p_u+q_u,A+B=P+Q,C=P+A-B\)。
那么只要支持 \(x\gets x\pm 1\) 或 \((A,B)\gets (A+1,B-1)\) 的操作即可,根据组合意义得到 \(f(A,B,C,x)=\sum_{i>A}\binom{i-1}{x}\binom{A+B-i}{C-x-1}\),因此容易 \(\mathcal O(1)\) 维护。
每个 \(u\) 的计算继承重儿子的结果即可。
时间复杂度 \(\mathcal O(n\log n)\)。
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=1e5+5,MOD=1e9+7;
ll fac[MAXN*2],ifac[MAXN*2];
ll ksm(ll a,ll b=MOD-2) { ll s=1; for(;b;a=a*a%MOD,b>>=1) if(b&1) s=s*a%MOD; return s; }
ll C(int x,int y) { return y<0||y>x?0:fac[x]*ifac[y]%MOD*ifac[x-y]%MOD; }
int n,a[MAXN],b[MAXN],p[MAXN],q[MAXN],hson[MAXN],sz[MAXN];
char s[MAXN],t[MAXN];
vector <int> G[MAXN],dfn;
struct ds {
int a,b,c,x; ll z;
ll ask(int ia,int ib,int ic,int ix) {
if(x==-1) {
a=ia,b=ib,c=ic,x=ix;
for(int i=0;i<=x;++i) z=(z+C(a,i)*C(b,c-i))%MOD;
return z;
}
if(ix==ic) {
a=ia,b=ib,c=ic,x=ix;
return z=C(a+b,c);
}
for(;x<ix;++x) z=(z+C(a,x+1)*C(b,c-x-1))%MOD;
for(;x>ix;--x) z=(z+(MOD-C(a,x))*C(b,c-x))%MOD;
for(;a<ia;++a,--b) z=(z+(MOD-C(a,x))*C(b-1,c-x-1))%MOD;
return z;
}
} f[MAXN],g[MAXN];
void dfs(int u,int fz,int d) {
p[u]=s[u]=='?',q[u]=t[u]=='?',a[u]=(s[u]=="01"[d]),b[u]=(t[u]=="01"[d]),sz[u]=1;
for(int v:G[u]) if(v^fz) {
dfs(v,u,d^1),p[u]+=p[v],q[u]+=q[v],a[u]+=a[v],b[u]+=b[v],sz[u]+=sz[v];
if(sz[hson[u]]<sz[v]) hson[u]=v;
}
dfn.push_back(u);
}
void solve() {
cin>>n,dfn.clear();
for(int i=1;i<=n;++i) G[i].clear(),hson[i]=0;
for(int i=1,u,v;i<n;++i) cin>>u>>v,G[u].push_back(v),G[v].push_back(u);
cin>>(s+1)>>(t+1),dfs(1,0,0);
ll ans=0;
for(int o:{0,1}) {
for(int i=0;i<=n;++i) f[i]=g[i]={-1,-1,-1,-1,0};
for(int u:dfn) {
f[u]=f[hson[u]],g[u]=g[hson[u]];
int mx=min(q[u]+p[u],a[u]-b[u]+p[u])%MOD;
if(mx>=0) ans=(ans+(a[u]-b[u]+p[u])*f[u].ask(p[u]+q[u],p[1]-p[u]+q[1]-q[u],p[1]+a[1]-b[1],mx))%MOD;
if(mx>0) ans=(ans+(MOD-p[u]-q[u])*g[u].ask(p[u]+q[u]-1,p[1]-p[u]+q[1]-q[u],p[1]+a[1]-b[1]-1,mx-1))%MOD;
}
for(int i=1;i<=n;++i) swap(p[i],q[i]),swap(a[i],b[i]);
}
cout<<(ans+MOD)%MOD<<"\n";
}
signed main() {
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
for(int i=fac[0]=1;i<MAXN*2;++i) fac[i]=fac[i-1]*i%MOD;
ifac[MAXN*2-1]=ksm(fac[MAXN*2-1]);
for(int i=MAXN*2-1;i;--i) ifac[i-1]=ifac[i]*i%MOD;
int _; cin>>_;
while(_--) solve();
return 0;
}
K. [QOJ10169] Nomad Camp (4)
可以用 Dijkstra \(\mathcal O(m\log m)\) 维护每个点在每种颜色下的后继。
然后猜测一个结论:合法方案存在当且节点对于任意点对 \((u,v)\) 都存在一种方案使得他们相遇,证明官方题解也没写。
可以从所有 \((x,x)\) 出发在反图上搜索。
时间复杂度 \(\mathcal O(n^2+m\log m)\)。
代码:
#include<bits/stdc++.h>
using namespace std;
const int MAXN=205,inf=1e9;
struct Edge { int v,w; };
vector <Edge> G[MAXN];
int n,m,c[MAXN],d[MAXN],f[MAXN],g[MAXN];
bool vis[MAXN],h[MAXN*MAXN];
basic_string <int> E[MAXN*MAXN];
int id(int x,int y) { return (min(x,y)-1)*n+max(x,y)-1; }
void dfs(int x) { h[x]=true; for(int y:E[x]) if(!h[y]) dfs(y); }
void solve() {
cin>>n>>m;
for(int i=1;i<=n;++i) cin>>c[i],--c[i],g[i]=0,G[i].clear();
for(int i=0;i<n*n;++i) E[i].clear(),h[i]=0;
for(int i=1,u,v,w;i<=m;++i) cin>>u>>v>>w,G[u].push_back({v,w}),G[v].push_back({u,w});
for(int o:{0,1,2,3}) {
priority_queue <array<int,2>,vector<array<int,2>>,greater<>> Q;
for(int i=1;i<=n;++i) {
vis[i]=0,d[i]=inf;
if(c[i]==o) Q.push({d[i]=0,i}),f[i]=i;
}
if(Q.empty()) continue;
while(Q.size()) {
int u=Q.top()[1]; Q.pop();
if(vis[u]) continue; vis[u]=true;
for(auto e:G[u]) {
if(d[e.v]>d[u]+e.w) f[e.v]=f[u],Q.push({d[e.v]=d[u]+e.w,e.v});
else if(d[e.v]==d[u]+e.w) f[e.v]=min(f[e.v],f[u]);
}
}
if(count(vis+1,vis+n+1,0)) return cout<<"NO\n",void();
for(int i=1;i<=n;++i) for(int j=i+1;j<=n;++j) E[id(f[i],f[j])].push_back({id(i,j)});
}
for(int i=1;i<=n;++i) dfs(id(i,i));
for(int i=1;i<=n;++i) for(int j=i+1;j<=n;++j) if(!h[id(i,j)]) return cout<<"NO\n",void();
cout<<"YES\n";
}
signed main() {
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
int _; cin>>_;
while(_--) solve();
return 0;
}
L. [QOJ7508] Fast Debugger (4)
考虑维护一个程序的信息,注意到每位独立,因此可以对每位维护 \((a,b,c,d)\) 的 \(2^4\) 种取值之间的置换。
由于 \(k\le 10^9\),因此我们只要保留前 \(k\) 条语句,那么把语句建树后更新每个循环的实际执行次数,并且压缩执行次数为 \(1\) 的循环。
最终得到的树上循环嵌套次数不超过 \(\mathcal O(\log k)\)。
那么维护每个节点所有儿子的前缀和,查询的时候逐层二分,可以预处理每个信息的 \(2^0\sim 2^{\log k}\) 次方加快查询速度。
时间复杂度 \(\mathcal O(nv2^c\log k+qv\log k+q\log^2k)\),其中 \(v=8,c=4\)。
代码:
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=12005;
const ll inf=1e9+1;
typedef array<char,16> info;
inline info operator *(const info &u,const info &v) { info w; for(int i=0;i<16;++i) w[i]=v[u[i]]; return w; }
const info I={0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15};
info ksm(info a,int b) { info s=I; for(;b;a=a*a,b>>=1) if(b&1) s=s*a; return s; }
vector <int> G[MAXN],E[MAXN];
array<info,8> f[MAXN],g[MAXN];
vector <array<info,8>> pw[MAXN];
int st[MAXN],c[MAXN];
ll sz[MAXN],sw[MAXN];
bool sg[MAXN];
void dfs1(int u,int fz,ll k) {
if(sg[u]) return E[fz].push_back(u);
if(c[u]>1) E[fz].push_back(u),fz=u;
for(int v:G[u]) if(sz[v]) {
if(sz[v]*c[v]<=k) dfs1(v,fz,sz[v]*c[v]),k-=sz[v]*c[v];
else {
c[v]=(k-1)/sz[v]+1;
dfs1(v,fz,c[v]>1?sz[v]:k);
break;
}
}
}
void dfs2(int u) {
if(sg[u]) return sz[u]=1,void();
sz[u]=0; for(int x=0;x<8;++x) f[u][x]=I;
for(int v:E[u]) {
dfs2(v),sz[u]=min(sz[u]+sz[v]*c[v],inf),sw[v]=sz[u];
for(int x=0;x<8;++x) f[u][x]=f[u][x]*ksm(f[v][x],c[v]),g[v][x]=f[u][x];
}
pw[u].resize(__lg(c[u])+1),pw[u][0]=f[u];
for(int i=1;i<(int)pw[u].size();++i) for(int x=0;x<8;++x) pw[u][i][x]=pw[u][i-1][x]*pw[u][i-1][x];
}
int z[8];
void qry(int u,ll k) {
if(!k) return ;
if(sg[u]) {
for(int x=0;x<8;++x) z[x]=f[u][x][z[x]];
return ;
}
int x=E[u].size()-1;
for(int i=1<<__lg(x);i;i>>=1) if(x>=i&&sw[E[u][x-i]]>=k) x-=i;
if(x) {
k-=sw[E[u][x-1]];
for(int i=0;i<8;++i) z[i]=g[E[u][x-1]][i][z[i]];
}
int v=E[u][x],h=k/sz[v];
if(!h||sg[v]) return qry(v,k);
for(int i=0;i<(int)pw[v].size();++i) if(h>>i&1) {
for(int j=0;j<8;++j) z[j]=pw[v][i][j][z[j]];
}
qry(v,k%sz[v]);
}
signed main() {
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
int n,q; cin>>n>>q,c[0]=1;
for(int i=1,l,r,tp=0;i<=n;++i) {
string o,t; cin>>o;
if(o=="repeat") {
st[++tp]=i,cin>>c[i];
} else if(o=="end") {
int u=st[tp--];
for(int v:G[u]) sz[u]=min(sz[u]+sz[v]*c[v],inf);
G[st[tp]].push_back(u);
} else {
sz[i]=c[i]=1,sg[i]=true,G[st[tp]].push_back(i);
cin>>t,l=t[0]-'a';
if(o=="or") {
cin>>t,r=t[0]-'a';
for(int y=0;y<8;++y) for(int x=0;x<16;++x) f[i][y][x]=x|((x>>r&1)<<l);
} else if(o=="and") {
cin>>t,r=t[0]-'a';
for(int y=0;y<8;++y) for(int x=0;x<16;++x) f[i][y][x]=x>>r&1?x:(x^(x&(1<<l)));
} else if(o=="xor") {
cin>>t,r=t[0]-'a';
for(int y=0;y<8;++y) for(int x=0;x<16;++x) f[i][y][x]=x^((x>>r&1)<<l);
} else if(o=="ori") {
cin>>r;
for(int y=0;y<8;++y) for(int x=0;x<16;++x) f[i][y][x]=x|((r>>y&1)<<l);
} else if(o=="andi") {
cin>>r;
for(int y=0;y<8;++y) for(int x=0;x<16;++x) f[i][y][x]=r>>y&1?x:(x^(x&(1<<l)));
} else if(o=="xori") {
cin>>r;
for(int y=0;y<8;++y) for(int x=0;x<16;++x) f[i][y][x]=x^((r>>y&1)<<l);
}
}
}
dfs1(0,0,inf),dfs2(0);
for(int k,w[4];q--;) {
cin>>k>>w[0]>>w[1]>>w[2]>>w[3];
for(int i=0;i<8;++i) {
z[i]=0;
for(int j:{0,1,2,3}) z[i]|=(w[j]>>i&1)<<j;
}
qry(0,k);
for(int j:{0,1,2,3}) {
w[j]=0;
for(int i=0;i<8;++i) w[j]|=(z[i]>>j&1)<<i;
}
cout<<w[0]<<" "<<w[1]<<" "<<w[2]<<" "<<w[3]<<"\n";
}
return 0;
}
M. [QOJ10019] Gold Coins (6.5)
观察有解矩形可以归纳得到:任意合法解当且节点去掉空行空列后存在一个 \((x,y)\) 使得:
- \([1,x]\times [1,y]\) 全部是 \(1\),\([x+1,n]\times [y+1,m]\) 全部是 \(0\)。
- \([1,x]\times [y+1,m],[x+1,n]\times [1,y]\) 分别合法。
证明可以参见 ZSH 的博客。
那么直接对子矩阵 dp,注意到 \([1,x]\times [y+1,m]\) 合法能推出 \([1,n]\times [y+1,m]\) 合法,因此只要 \(f_{i,j}\) 表示 \([i,n]\times [j,m]\) 合法的最小代价即可。
转移的时候枚举 \(x\),\(y\) 显然是前 \(x\) 行最后一个 \(1\) 的所在列,\(y\) 更大的决策在 \(f_{i,y+1}\) 种考虑到了。
时间复杂度 \(\mathcal O(n^2m)\)。
代码:
#include<bits/stdc++.h>
using namespace std;
const int MAXN=305;
int n,m,a[MAXN][MAXN],b[MAXN][MAXN],h[MAXN],f[MAXN][MAXN],u[MAXN],v[MAXN];
int c(int l,int r,int x,int y) { return b[r][y]-b[l-1][y]-b[r][x-1]+b[l-1][x-1]; }
signed main() {
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
cin>>n>>m;
for(int i=1;i<=n;++i) for(int j=1;j<=m;++j) {
cin>>a[i][j],b[i][j]=b[i-1][j]+b[i][j-1]-b[i-1][j-1]+a[i][j],h[i]=(a[i][j]?j:h[i]);
}
for(int i=n;i;--i) h[i]=max(h[i],h[i+1]);
for(int i=n;i;--i) for(int j=m;j;--j) {
f[i][j]=1e9,u[i-1]=v[j-1]=0;
for(int x=i;x<=n;++x) u[x]=u[x-1]+(!!c(x,x,j,m));
for(int y=j;y<=m;++y) v[y]=v[y-1]+(!!c(i,n,y,y));
for(int x=i,y;x<=n;++x) y=max(j,h[x+1]),f[i][j]=min(f[i][j],u[x]*v[y]-c(i,x,j,y)+f[x+1][j]+f[i][y+1]);
}
cout<<f[1][1]<<"\n";
return 0;
}
N. [QOJ6647] Slot (5.5)
随机游走求期望步数问题,考虑拆成到达每个非终止点的概率之和。
枚举状态 \((S,k)\) 表示 \(k\) 次操作后当前为 \(1\) 的集合是 \(S\),对应概率为 \(\sum_{S,k}\prod_{i\in S}(1-q_i^k)\prod_{i\not\in S}q_i^k\),其中 \(q_i=1-p_i\)。
展开得到 \(\sum_{S}\sum_{T\subseteq S}(-1)^{|T|}\sum_k\prod_{i\in T\cup\overline{S}}q_i^k=\sum_{T\subseteq S}(-1)^T\dfrac{1}{1-\prod_{i\in{T\cup\overline S}}q_i}\)。
求出 \(p_s=\dfrac{1}{1-\prod_{i\in s}q_i}\) 之后 FWT 可以得到每个 \(S\) 的贡献 \(f_S\)。
然后我们要对每个可能的答案串 \(w\),计算所有 \(S\) 使得 \(S\cap w\) 唯一的 \(\sum f_S\),注意到这样的 \((w,S\cap w)\) 最多 \(\mathcal O(3^n)\) 对,因此 dfs 枚举 \(w\),对于每种 \(S\cap w\) 动态维护对应 \(w\) 是否唯一。
时间复杂度 \(\mathcal O(n2^n+3^n)\)。
代码:
#include<bits/stdc++.h>
using namespace std;
const int MAXN=1<<15|5,MOD=998244353;
void sub(int &x,const int &y) { x=(x>=y)?x-y:x+MOD-y; }
int ksm(int a,int b=MOD-2) { int s=1; for(;b;a=1ll*a*a%MOD,b>>=1) if(b&1) s=1ll*s*a%MOD; return s; }
int n,m,a[MAXN],b[16][MAXN],f[MAXN],g[MAXN];
void dfs(int s,int p,int e) {
int *c=b[e];
for(int t=s;;t=(t-1)&s) {
if(c[t]>0) sub(g[c[t]],f[s]);
if(!t) break;
}
if(!s) return ;
int *d=b[e+1];
for(int i=p;i<n;++i) if(s>>i&1) {
for(int t=s;;t=(t-1)&s) {
if(t>>i&1) d[t^(1<<i)]=c[t];
else d[t]=(d[t]&&c[t])?-1:(d[t]|c[t]);
if(!t) break;
}
dfs(s^(1<<i),i+1,e+1);
}
}
void solve() {
cin>>n>>m,memset(b,0,sizeof(b));
for(int s=0;s<(1<<n);++s) f[s]=1;
for(int i=0,w=ksm(10000),x;i<n;++i) {
cin>>x,f[1<<i]=(1+1ll*(MOD-w)*x)%MOD;
}
for(int i=1;i<(1<<n);i<<=1) for(int s=0;s<(1<<n);++s) if(s&i) f[s]=1ll*f[s]*f[s^i]%MOD;
for(int s=0;s<(1<<n);++s) f[s]=ksm(1+MOD-f[s]);
for(int i=1;i<(1<<n);i<<=1) for(int s=0;s<(1<<n);++s) if(s&i) {
swap(f[s],f[s^i]),sub(f[s],f[s^i]);
}
int w=0;
for(int s=0;s<(1<<n);++s) w=(w+f[s])%MOD;
for(int i=1;i<=m;++i) {
string o; cin>>o,a[i]=0,g[i]=w;
for(int j=0;j<n;++j) a[i]|=(o[j]-'0')<<j;
b[0][a[i]]=i;
}
dfs((1<<n)-1,0,0);
for(int i=1;i<=m;++i) cout<<g[i]<<"\n";
}
signed main() {
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
int _; cin>>_;
while(_--) solve();
return 0;
}
O. [QOJ6103] A+B Problem (3)
题意就是构造 Halin 图的树分解,对每个点 \(u\) 维护子树中编号最小的叶子 \(l_u\),以及编号最大的叶子的后继 \(r_u\)。
每个 \(u\) 建立节点 \((u,fa_u,l_u,r_u)\),和儿子 \(v\) 合并时建立 \((u,l_u,r_u,r_v)\) 中转。
可以证明点数 \(\le 2n\)。
时间复杂度 \(\mathcal O(n)\)。
代码:
#include<bits/stdc++.h>
using namespace std;
const int MAXN=1e5+5;
int n,m,fa[MAXN*4],l[MAXN],r[MAXN],id[MAXN],nx[MAXN];
vector <int> G[MAXN],S[MAXN*4];
void dfs(int u,int fz) {
if(G[u].empty()) {
id[u]=++m,S[m]={u,fz,nx[u]},l[u]=u,r[u]=nx[u];
return ;
}
for(int v:G[u]) {
dfs(v,u);
if(!id[u]) { id[u]=id[v],l[u]=l[v],r[u]=r[v]; continue; }
S[++m]={u,l[u],r[u],r[v]};
fa[id[u]]=fa[id[v]]=m,r[u]=r[v],id[u]=m;
}
if(fz) S[++m]={u,fz,l[u],r[u]},fa[id[u]]=m,id[u]=m;
}
signed main() {
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
cin>>n;
for(int i=2,x;i<=n;++i) cin>>x,G[x].push_back(i);
vector <int> h;
for(int i=1;i<=n;++i) if(G[i].empty()) h.push_back(i);
h.push_back(h[0]);
for(int i=0;i+1<(int)h.size();++i) nx[h[i]]=h[i+1];
dfs(1,0);
cout<<m<<"\n";
for(int i=1;i<=m;++i) {
sort(S[i].begin(),S[i].end());
S[i].erase(unique(S[i].begin(),S[i].end()),S[i].end());
cout<<S[i].size();
for(int x:S[i]) cout<<" "<<x;
cout<<"\n";
}
for(int i=1;i<=m;++i) if(fa[i]) cout<<fa[i]<<" "<<i<<"\n";
return 0;
}
*P. [QOJ8005] Crossing the Border (7)
直接对 \(2^n\) 种状态做子集 Exp 难以接受,考虑折半。
按 \(c\) 降序排序,把物品集合拆成前一半和后一半两部分,状态为 \(f_{s,t}\)。
转移的集合也分成 \(x,y\) 两半,要求 \(w_x+w_y\le W\),这是经典的双指针形式。
注意到集合 \((x,y)\) 的信息只和 \(x\) 中 \(\max c\) 有关,因此可以转移 \(f_{s,t-y}\to f_{s+x,t}\),在枚举 \(x\) 的同时维护合法的 \(y\) 对应 \(f_{s,t-y}\) 之和。
提前给每个 \(s,t\) 的所有子集排序即可,记得预处理 \(x=\varnothing\) 的集合贡献。
时间复杂度 \(\mathcal O(n3^{n/2}+2^{n/2}\times 3^{n/2})\)。
代码:
#include<bits/stdc++.h>
#define lb __builtin_ctz
using namespace std;
const int inf=1.1e9+7,MOD=998244353;
struct info {
int v,f;
info(int V=inf,int F=0): v(V),f(F) {}
inline friend info operator *(const info &u,const info &v) {
return info(u.v+v.v,1ll*u.f*v.f%MOD);
}
inline friend info operator +(const info &u,const info &v) {
if(u.v!=v.v) return u.v<v.v?u:v;
return {u.v,(u.f+v.f)%MOD};
}
} f[1<<11][1<<11];
struct item { int w,c; } a[22];
int n,m,l,r,sl[1<<11],sr[1<<11];
basic_string <array<int,2>> br[1<<11];
array<int,2> bl[1<<11];
signed main() {
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
cin>>n>>m,l=n/2,r=n-n/2;
for(int i=0;i<n;++i) cin>>a[i].w>>a[i].c;
sort(a,a+n,[&](auto x,auto y){ return x.c>y.c; });
for(int i=0;i<(1<<l);++i) for(int j=0;j<l;++j) if(i>>j&1) sl[i]+=a[j].w;
for(int i=0;i<(1<<r);++i) for(int j=0;j<r;++j) if(i>>j&1) sr[i]+=a[j+l].w;
info *g=f[0]; g[0]={0,1};
for(int s=1;s<(1<<r);++s) for(int t=s,x=lb(s);t;t=(t-1)&s) if((t>>x&1)&&sr[t]<=m) {
g[s]=g[s]+g[s^t]*info(a[x+l].c,1);
}
for(int s=0;s<(1<<r);++s) {
for(int t=s;;t=(t-1)&s) { br[s].push_back({sr[t],t}); if(!t) break; }
sort(br[s].begin(),br[s].end(),greater<>());
}
for(int s=1;s<(1<<l);++s) {
int q=0,d=lb(s);
for(int t=s;t;t=(t-1)&s) if(t>>d&1) bl[q++]={sl[t],t};
sort(bl,bl+q);
for(int t=0;t<(1<<r);++t) {
int y=0; info w;
for(auto x:br[(1<<r)-1-t]) {
for(;y<q&&x[0]+bl[y][0]<=m;++y) w=w+f[s-bl[y][1]][t]*info(a[d].c,1);
f[s][t+x[1]]=f[s][t+x[1]]+w;
}
}
}
auto z=f[(1<<l)-1][(1<<r)-1];
cout<<z.v<<" "<<z.f<<"\n";
return 0;
}
Q. [QOJ7563] Fun on Tree (2)
\(f_u\) 表示子树内最小的 \(dep_x-a_x\),\(g_u\) 表示 \(T_u\setminus T_{hson(u)}\) 中最小的 \(dep_x-a_x-2dep_u\),修改就是子树加,然后更新轻祖先的 \(g\)。
查询时维护链上的 \(\min g_u\) 就得到了答案。
时间复杂度 \(\mathcal O(q\log^2n)\)。
代码:
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=2e5+5;
const ll inf=1e18;
typedef array<ll,2> pii;
int n,m;
struct Segt1 {
pii tr[1<<19]; ll tg[1<<19];
void adt(int p,ll k) { tr[p][0]+=k,tg[p]+=k; }
void psu(int p) { tr[p]=max(tr[p<<1],tr[p<<1|1]); }
void psd(int p) { if(tg[p]) adt(p<<1,tg[p]),adt(p<<1|1,tg[p]),tg[p]=0; }
void add(int ul,int ur,ll k,int l=1,int r=n,int p=1) {
if(ul<=l&&r<=ur) return adt(p,k);
int mid=(l+r)>>1; psd(p);
if(ul<=mid) add(ul,ur,k,l,mid,p<<1);
if(mid<ur) add(ul,ur,k,mid+1,r,p<<1|1);
psu(p);
}
void upd(int u,pii z,int l=1,int r=n,int p=1) {
if(l==r) return tr[p]=z,void();
int mid=(l+r)>>1; psd(p);
u<=mid?upd(u,z,l,mid,p<<1):upd(u,z,mid+1,r,p<<1|1);
psu(p);
}
pii qry(int ul,int ur,int l=1,int r=n,int p=1) {
if(ul>ur) return {-inf,-inf};
if(ul<=l&&r<=ur) return tr[p];
int mid=(l+r)>>1; pii s={-inf,-inf}; psd(p);
if(ul<=mid) s=max(s,qry(ul,ur,l,mid,p<<1));
if(mid<ur) s=max(s,qry(ul,ur,mid+1,r,p<<1|1));
return s;
}
} T,Q;
struct Edge { int v,w; };
vector <Edge> G[MAXN];
ll a[MAXN],d[MAXN];
int siz[MAXN],hson[MAXN],fa[MAXN],top[MAXN],dfn[MAXN],dcnt,efn[MAXN];
void dfs1(int u,int fz) {
fa[u]=fz,siz[u]=1;
for(auto e:G[u]) {
d[e.v]=d[u]+e.w,dfs1(e.v,u),siz[u]+=siz[e.v];
if(siz[e.v]>siz[hson[u]]) hson[u]=e.v;
}
}
void upd(int u) {
pii x=T.qry(dfn[u],dfn[u]);
if(hson[u]) x=max(x,T.qry(efn[hson[u]]+1,efn[u]));
Q.upd(dfn[u],{x[0]-2*d[u],x[1]});
}
void dfs2(int u,int h) {
top[u]=h,dfn[u]=++dcnt,T.upd(dfn[u],{d[u]-a[u],-u});
if(hson[u]) dfs2(hson[u],h);
for(auto e:G[u]) if(e.v!=hson[u]) dfs2(e.v,e.v);
efn[u]=dcnt,upd(u);
}
signed main() {
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
cin>>n>>m;
for(int i=1;i<=n;++i) cin>>a[i];
for(int i=2,x,y;i<=n;++i) cin>>x>>y,G[x].push_back({i,y});
dfs1(1,0),dfs2(1,1);
for(int x,y,z;m--;) {
cin>>x>>y>>z;
T.add(dfn[y],efn[y],-z),Q.add(dfn[y],efn[y],-z);
for(int u=y;u;u=fa[top[u]]) upd(u);
pii s=T.qry(dfn[x],efn[x]); s[0]-=2*d[x];
for(int u=x;u;u=fa[u]) {
s=max(s,Q.qry(dfn[top[u]],dfn[u]-1)),u=top[u];
if(u>1) {
pii o=max(T.qry(dfn[fa[u]],dfn[u]-1),T.qry(efn[u]+1,efn[fa[u]]));
s=max(s,{o[0]-2*d[fa[u]],o[1]});
}
}
cout<<-s[1]<<" "<<s[0]+d[x]<<"\n";
}
return 0;
}
R. [QOJ10174] Lost Table (2)
容斥相当于选一些 \(a_i,b_i\) 减一,然后维护 \(\prod_{i,j}\min(a_i,b_j)\),从小到大枚举每个值 \(v\),容易算出有多少 \(\min(a_i,b_j)=v\)。
考虑给一些 \(a_i,b_i\) 减一不影响 \(v\) 之间的大小关系,对每个 \(v\) 内部选一些 \(a_i,b_j\) 减一,然后先算 \(v-1\) 的贡献再算 \(v\) 的贡献。
简单处理可以把 \(b_j\) 个数的枚举用二项式定理优化掉。
时间复杂度 \(\mathcal O(n+m)\)。
代码:
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=2e5+5,MOD=1e9+7;
ll fac[MAXN],ifac[MAXN];
ll ksm(ll a,ll b=MOD-2) { ll s=1; for(;b;a=a*a%MOD,b>>=1) if(b&1) s=s*a%MOD; return s; }
ll C(int x,int y) { return y<0||y>x?0:fac[x]*ifac[y]%MOD*ifac[x-y]%MOD; }
signed main() {
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
for(int i=fac[0]=ifac[0]=1;i<MAXN;++i) ifac[i]=ksm(fac[i]=fac[i-1]*i%MOD);
int n,m; cin>>n>>m;
map <int,array<int,2>> a;
for(int i=1,x;i<=n;++i) cin>>x,++a[x][0];
for(int i=1,x;i<=m;++i) cin>>x,++a[x][1];
ll z=1,s=n,t=m;
for(auto it:a) {
int v=it.first,x=it.second[0],y=it.second[1];
ll w=0;
for(int i=0;i<=x;++i) {
ll b=(MOD-ksm(v-1,s-i))*ksm(v,MOD-1+i-s)%MOD;
w=(w+(i&1?-1:1)*ksm(b+1,y)*C(x,i)%MOD*ksm(v-1,i*t)%MOD*ksm(v,(x-i)*t+y*(s-x)))%MOD;
}
z=z*(w+MOD)%MOD,s-=x,t-=y;
}
cout<<z<<"\n";
return 0;
}
*S. [QOJ6653] 阴阳阵法 (7)
考虑 \(f_{x,y}\) 表示 \(x\) 个白点 \(y\) 个黑点的方案数,初始 \(f_{x,0}=(n+m)^x\),初始令 \(f_{x,y}=f_{x,y-1}\times n\),然后容斥掉加入最后一个黑点时产生环的方案数。
具体来说枚举环上黑白点个数 \(i,j\),然后组合数计算方案,复杂度 \(\mathcal O(n^4)\)。
考虑优化,设 \(g_{x,y,0/1,0/1,0/1}\) 表示容斥过程中(算上环外节点)有 \(x\) 个白点 \(y\) 个黑点,两种点奇偶性为 \(0/1\),当前环为颜色为黑或白的方案数,每次加入一个环末节点即可。
容斥就是 \(f_{x,y}\) 减掉 \(g_{x,y-1,1,0,0}\),即插入最后一个黑点后成环的情况,注意我们要钦定 \(g\) 中计算的环首是白点。
时间复杂度 \(\mathcal O(n^2)\)。
代码:
#include<bits/stdc++.h>
using namespace std;
const int MAXN=2005;
int MOD,n,m,f[MAXN][MAXN],g[MAXN][MAXN][2][2][2];
signed main() {
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
cin>>n>>m>>MOD;
for(int i=0;i<=n;++i) {
f[i][0]=i?1ll*f[i-1][0]*(n+m)%MOD:1;
for(int j=1;j<=m;++j) {
f[i][j]=(1ll*n*f[i][j-1]+MOD-g[i][j-1][1][0][0])%MOD;
}
for(int j=0;j<=m;++j) {
g[i+1][j][1][0][0]=(g[i+1][j][1][0][0]+1ll*(i+1)*f[i][j])%MOD;
for(int x:{0,1}) for(int y:{0,1}) {
g[i+1][j][x^1][y][0]=(g[i+1][j][x^1][y][0]+1ll*(i+1)*(g[i][j][x][y][0]+g[i][j][x][y][1]))%MOD;
g[i][j+1][x][y^1][1]=(g[i][j+1][x][y^1][1]+1ll*(j+1)*g[i][j][x][y][0])%MOD;
}
}
}
cout<<f[n][m]<<"\n";
return 0;
}
T. [QOJ6101] Ring Road (3.5)
Halin 图上最短路,建立树分解后点分治,对重心上的每个节点跑一遍 Dijkstra。
时间复杂度 \(\mathcal O((n+q)\log n)\)。
代码:
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=2e5+5;
int n,m,q,l[MAXN],r[MAXN],id[MAXN],nx[MAXN];
struct Edge { int v; ll w; };
vector <Edge> G[MAXN];
vector <int> S[MAXN],E[MAXN];
void link(int x,int y) { E[x].push_back(y),E[y].push_back(x); }
void dfs1(int u,int fz) {
if(G[u].size()==1&&fz) {
id[u]=++m,S[m]={u,fz,nx[u]},l[u]=u,r[u]=nx[u];
return ;
}
for(auto e:G[u]) if(e.v^fz) {
int v=e.v; dfs1(v,u);
if(!id[u]) { id[u]=id[v],l[u]=l[v],r[u]=r[v]; continue; }
S[++m]={u,l[u],r[u],r[v]};
link(m,id[u]),link(m,id[v]),r[u]=r[v],id[u]=m;
}
if(fz) S[++m]={u,fz,l[u],r[u]},link(m,id[u]),id[u]=m;
}
bool vis[MAXN],inq[MAXN],tmp[MAXN];
int fa[MAXN],b[MAXN],siz[MAXN],cur[MAXN],st[MAXN],tp,ed[MAXN];
ll dis[100][MAXN];
void solve(int u) {
function<void(int,int)> dfs4=[&](int x,int fz) {
siz[x]=1;
for(int o:S[x]) if(!tmp[o]&&!inq[o]) tmp[o]=true,st[++tp]=o;
for(int y:E[x]) if(!vis[y]&&y!=fz) dfs4(y,x),siz[x]+=siz[y];
};
dfs4(u,0); int o=b[u]*4;
for(int s:S[u]) if(tmp[s]) {
ll *d=dis[o++];
priority_queue<array<ll,2>,vector<array<ll,2>>,greater<>> Q;
Q.push({d[s]=0,s});
while(Q.size()) {
ll z=Q.top()[0],x=Q.top()[1]; Q.pop();
if(z!=d[x]) continue;
vector <Edge> h;
for(auto e:G[x]) if(tmp[e.v]) {
h.push_back(e);
if(d[e.v]>d[x]+e.w) Q.push({d[e.v]=d[x]+e.w,e.v});
}
G[x].swap(h);
}
tmp[s]=false,inq[s]=true,ed[s]=u;
}
while(tp) tmp[st[tp--]]=0;
}
void dfs2(int u) {
solve(u),vis[u]=true;
for(int v:E[u]) if(!vis[v]) {
int rt=0;
function<void(int,int)> dfs3=[&](int x,int fz) {
cur[x]=siz[v]-siz[x];
for(int y:E[x]) if(y!=fz&&!vis[y]) dfs3(y,x),cur[x]=max(cur[x],siz[y]);
if(!rt||cur[rt]>cur[x]) rt=x;
};
dfs3(v,u),b[rt]=b[u]+1,fa[rt]=u,dfs2(rt);
}
}
signed main() {
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
cin>>n; ll z;
for(int i=2,x;i<=n;++i) cin>>x>>z,G[x].push_back({i,z}),G[i].push_back({x,z});
vector <int> h;
for(int i=2;i<=n;++i) if(G[i].size()==1) h.push_back(i);
h.push_back(h[0]);
for(int i=0;i+1<(int)h.size();++i) nx[h[i]]=h[i+1];
dfs1(1,0);
for(int i=0;i+1<(int)h.size();++i) cin>>z,G[h[i]].push_back({h[i+1],z}),G[h[i+1]].push_back({h[i],z});
for(int i=1;i<=m;++i) {
sort(S[i].begin(),S[i].end());
S[i].erase(unique(S[i].begin(),S[i].end()),S[i].end());
}
memset(dis,0x3f,sizeof(dis)),dfs2(1);
cin>>q;
for(int u,v,x,y;q--;) {
cin>>u>>v,z=1e18;
for(x=ed[u],y=ed[v];x^y;b[x]<b[y]?y=fa[y]:x=fa[x]);
for(int i=b[x]*4+3;~i;--i) z=min(z,dis[i][u]+dis[i][v]);
cout<<z<<"\n";
}
return 0;
}
U. [QOJ9699] Loving You in My Humble Way (6.5)
正宗 Ad-Hoc。
直接把一条边看成 \((x,y),(y,z),(z,x)\) 三条边,限制变成没有四元环。
考虑空间中的直线,如果两条直线垂直就连边,可以证明图上没有四元环。
那么取质数 \(p\),在 \(\bmod p\) 意义下取出所有不平行直线,垂直就连边,可以证明不存在四元环,可以通过把其中两条直线变成 \((1,0,0),(0,1,0)\) 证明。
此时点数 \(p^2+p+1\),取 \(p=43\),直接数图中的三元环个数发现恰好合法。
时间复杂度 \(\mathcal O\left(\dfrac{n^3}\omega\right)\)。
代码:
#include<bits/stdc++.h>
using namespace std;
const int n=43,M=1893;
array<int,3> a[M];
bitset <M> f[M],g;
signed main() {
int m=0; a[m++]={0,0,1};
for(int i=0;i<n;++i) a[m++]={0,1,i};
for(int i=0;i<n;++i) for(int j=0;j<n;++j) a[m++]={1,i,j};
cout<<n*(n-1)*(n+1)/6<<"\n";
for(int i=0;i<m;++i) for(int j=i+1;j<m;++j) if((a[i][0]*a[j][0]+a[i][1]*a[j][1]+a[i][2]*a[j][2])%n==0) f[i].set(j);
for(int i=0;i<m;++i) for(int j=i+1;j<m;++j) if(f[i][j]) {
g=f[i],g&=f[j];
for(int k=g._Find_first();k<M;k=g._Find_next(k)) cout<<i+1<<" "<<j+1<<" "<<k+1<<"\n";
}
return 0;
}
V. [QOJ10167] Random Interactive MST Bot (4.5)
考虑稠密图上表现最优秀的 Prim 算法,注意到我们只要维护每个 \(u\) 到当前点集的最小值 \(d_u\),那么取出每次更新 \(d_u\) 的边权,\(d_u\) 的实际变化次数就是其中前缀最小值的期望个数,显然是 \(\mathcal O(\log n)\) 级别的。
用 zkw 线段树维护所有 \(d_u\),增加时自下而上更新区间 \(\min d_u\),如果更新不了可以直接返回。
加上朴素记忆化即可通过,交互次数在 \(5900\) 次左右。
时间复杂度 \(\mathcal O(n^2\log n)\)。
代码:
#include<bits/stdc++.h>
using namespace std;
typedef array<int,2> pii;
const int N=128,M=5005;
int L(pii x) { return min(x[0],x[1]); }
int R(pii x) { return max(x[0],x[1]); }
int n,m,id[N][N];
bitset <M> g[M],w[M];
bool ask(pii x,pii y) {
if(!x[0]||!y[0]) return x[0];
int u=id[L(x)][R(x)],v=id[L(y)][R(y)];
if(w[u][v]) return g[u][v];
cout<<"? "<<L(x)<<" "<<R(x)<<" "<<L(y)<<" "<<R(y)<<endl;
int o; cin>>o;
w[u][v]=w[v][u]=1,g[u][v]=o,g[v][u]=o^1;
return o;
}
bool vis[N];
pii d[N],f[N<<1];
void upd(int x,pii z) {
for(x+=N;x&&ask(z,f[x]);x>>=1) f[x]=z;
}
void del(int x) {
for(f[x+=N]={0,0};x>1;x>>=1) f[x>>1]=(ask(f[x],f[x^1])?f[x]:f[x^1]);
}
signed main() {
cin>>n;
for(int i=1;i<=n;++i) for(int j=i+1;j<=n;++j) id[i][j]=++m;
vis[1]=true;
for(int i=2;i<=n;++i) upd(i,d[i]={1,i});
vector <pii> z;
for(int t=1;t<n;++t) {
int u=f[1][1];
z.push_back({f[1][0],u});
vis[u]=true,del(u);
for(int i=1;i<=n;++i) if(!vis[i]) upd(i,{u,i});
}
cout<<"!"; for(auto t:z) cout<<" "<<L(t)<<" "<<R(t); cout<<endl;
return 0;
}
*W. [QOJ8012] Jumping Lights (8)
考虑同时维护当前树以及进行二操作后的树。
对于一个翻转操作我们将该点记录下来,在进行二操作前更新这些点的贡献。
具体来说对每个点分成父亲、叶子儿子、非叶子儿子,父亲节点可以在翻转时记录,叶子儿子直接对每个点记录个数并维护覆盖标记,非叶子儿子用哈希表维护未标记的部分并暴力更新。
可以通过势能分析证明复杂度是线性的。
时间复杂度 \(\mathcal O(n+q)\)。
代码:
#include<bits/stdc++.h>
#include<ext/pb_ds/assoc_container.hpp>
using namespace std;
using namespace __gnu_pbds;
const int MAXN=3e5+5;
vector <int> G[MAXN];
int n,q,fa[MAXN],nc[MAXN],lc[MAXN];
struct ds {
bool a[MAXN],b[MAXN];
basic_string <int> Q;
gp_hash_table <int,int> E[MAXN];
int sc[MAXN],vs[MAXN],vc,z;
bool qcol(int u) { return vs[fa[u]]>vs[u]?b[fa[u]]:a[u]; }
bool ncol(int u) { return a[fa[u]]||sc[u]||(int)E[u].size()<nc[u]; }
void updL(int u,int c) {//leaf
vs[u]=++vc,a[u]=c,z+=2*c-1,sc[fa[u]]+=2*c-1;
}
void updV(int u,int c) { //no leaf
a[u]=c,z+=2*c-1;
if(fa[u]) {
if(c) E[fa[u]].erase(u);
else E[fa[u]].insert({u,0});
}
}
void updN(int u,int c) { //neighbour
vs[u]=++vc,b[u]=c,z-=sc[u],sc[u]=c*lc[u],z+=sc[u];
}
} T[2];
void updL(int o,int u,int c) {
if(T[o].qcol(u)==c) return ;
T[0].Q+=fa[u],T[1].Q+=fa[u],T[o].updL(u,c);
}
void updV(int o,int u,int c) {
if(T[o].a[u]==c) return ;
T[0].Q+=u,T[1].Q+=u;
if(fa[u]) T[o].Q+=fa[u];
T[o].updV(u,c);
}
int vis[MAXN],vc=0;
void solve(int o) {
basic_string<int>Q; Q.swap(T[o].Q),++vc;
for(int u:Q) if(vis[u]<vc) {
int x=T[o].a[u],y=T[o].ncol(u);
updV(o^1,u,y),T[o^1].updN(u,x),vis[u]=vc;
if(x) {
gp_hash_table<int,int>E; E.swap(T[o^1].E[u]);
if(fa[u]) updV(o^1,fa[u],1);
for(auto i:E) updV(o^1,i.first,1);
}
}
}
bool il[MAXN];
void dfs(int u,int fz) {
il[u]=(fz&&G[u].size()==1),fa[u]=fz;
for(int v:G[u]) if(v^fz) {
dfs(v,u),lc[u]+=il[v],nc[u]+=!il[v];
if(!il[v]) for(int o:{0,1}) T[o].E[u].insert({v,0});
}
}
signed main() {
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
cin>>n>>q;
for(int i=1,u,v;i<n;++i) cin>>u>>v,G[u].push_back(v),G[v].push_back(u);
dfs(1,0);
for(int o=0,c,u;q--;) {
cin>>c;
if(c==2) solve(o),o^=1;
else cin>>u,(il[u]?updL(o,u,c):updV(o,u,c));
cout<<T[o].z<<" \n"[!q];
}
return 0;
}
*X. [QOJ6651] 喵了个喵 III (9)
考虑最后一对删除的元素 \(x\),那么序列被分成 \(AxBxC\)。
那么操作过程一定形如 \([A][x]\to[][x,B]\to [x,C][x,B]\to [x][x]\),注意到 \(A\) 中未消除的元素必须在同一个栈中,因此 \(A\) 中剩余的元素只有恰好出现过一次的元素,并且按顺序排列之。
考虑 \(A,B,C\) 三部分过程分开,可以定义状态 \(f_{l,r}\) 表示 \(a[1,l-1]\) 中出现过一次的元素放在其中一个栈,另一个栈为空,能否操作 \(a[l,r]\) 使得最终 \(a[1,r]\) 中出现过一次的元素放入另一个栈。
考虑 \(f\) 的转移,我们还需要 \(g_{l,r}\) 表示最终把 \(a[1,r]\) 中出现过一次的元素放入同一个栈。
以及 \(h_{l,r}\) 表示 \([1,r]\) 内部元素两两匹配时能否完全消除。
分讨一下转移:
- \(f\gets g+f\):记 \(x\) 为 \([l,r]\) 中第一个匹配 \([r+1,n]\) 的元素,序列为 \(A+BxC\),那么过程为 \([A][]\to [A,B][x]\to [][x,C]\)。
注意这里 \(g\) 操作要求 \(AB\) 中能匹配的点是 \(A\) 的一段栈顶,因此不能有 \(C\to i,B\to j\) 满足 \(j<i,i,j\in A\),否则 \(j\) 消不掉。
-
\(g\gets f+f\):同上定义 \(x,A,B,C\),过程为 \([A][]\to [A,x][B]\to [A,x,C][]\)。
这里要求 \(A,C\) 无边。
-
\(h\gets f+f+h\):枚举 \([l,r]\) 最后一对消除元素 \(x\),设序列为 \(A+BxCxD\),那么过程为:\([A][]\to [A,x][B]\to [A,x,C][B,x]\to [A,x][B,x]\)。
要求 \((A,C),(A,D),(B,D)\) 无边。
-
\(h\gets g+f+h\):同上定义 \(x,A,B,C,D\),过程为:\([A][]\to [A,B][x]\to [A,B,x][x,C]\to [A,B,x][x]\)。
要求 \((A,D),(B,D)\) 无边,并且类似情况一,\(C,D\) 中元素不占用 \(A\) 的栈顶。
-
\(h\gets g+h\):这种情况下依然枚举 \(x\),但与 \(x\) 匹配的元素 \(\in a[1,l-1]\),设序列为 \(A+BxC\),过程为:\([A][]\to [AB][x]\to [AB][xC]\to [A'x][x]\)。
这种情况下依然要求 \(C\) 不占用 \(A\) 的栈顶,且任何非 \(x\) 元素不能 \(\in A'\),直接取 \([l,r]\) 中 \(p\) 的最小值为 \(x\)。
注意到 \(h_{l,r}\) 本质是 \([l,r]\) 中没有向 \(>r\) 匹配元素时 \(f_{l,r}=g_{l,r}=h_{l,r}\),按照上述过程预处理判定条件然后暴力 dp,记录决策点然后模拟上述策略构造。
时间复杂度 \(\mathcal O(n^3)\)。
代码:
#include<bits/stdc++.h>
using namespace std;
const int MAXN=1505;
int n,a[MAXN],t[MAXN],p[MAXN],ls[MAXN][MAXN],rp[MAXN][MAXN],mn[MAXN][MAXN],mx[MAXN][MAXN],cv[MAXN][MAXN];
int h[MAXN][MAXN],b[MAXN];
bool f[MAXN][MAXN],g[MAXN][MAXN];
void dp(int l,int r,bool o,bool c) {
if(l>r) return ;
if(ls[l][r]<=r) {
int i=ls[l][r];
if(o) b[i]=c,dp(l,i-1,0,c),dp(i+1,r,0,c^1);
else b[i]=c^1,dp(l,i-1,1,c),dp(i+1,r,0,c);
return ;
}
int i=h[l][r];
if(i<=n) {
dp(l,i-1,0,c),b[i]=c,dp(i+1,p[i]-1,0,c^1),b[p[i]]=c^1,dp(p[i]+1,r,0,c);
} else if(i-n<=n) {
i-=n,dp(l,i-1,1,c),b[i]=c^1,dp(i+1,p[i]-1,0,c),b[p[i]]=c,dp(p[i]+1,r,0,c^1);
} else {
i-=2*n,dp(l,i-1,1,c),b[i]=c^1,dp(i+1,r,0,c);
}
}
signed main() {
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
cin>>n;
for(int i=1;i<=n;++i) {
cin>>a[i];
if(t[a[i]]) p[i]=t[a[i]],p[p[i]]=i;
else t[a[i]]=i;
}
for(int i=0;i<=n;++i) f[i+1][i]=g[i+1][i]=true;
memset(ls,0x3f,sizeof(ls));
memset(mn,0x3f,sizeof(mn));
memset(cv,0x3f,sizeof(cv));
for(int l=1;l<=n;++l) {
for(int r=l;r<=n;++r) {
rp[l][r]=p[r]<l?r:rp[l][r-1];
mn[l][r]=min(mn[l][r-1],p[r]);
mx[l][r]=max(mx[l][r-1],p[r]);
}
memset(t,0x3f,sizeof(t));
for(int r=n;r>=l;--r) {
for(int x=mn[l][r];x<=n;x+=x&-x) cv[l][r]=min(cv[l][r],t[x]);
if(p[r]<l) for(int x=p[r];x;x&=x-1) t[x]=r;
}
}
for(int r=1;r<=n;++r) for(int l=r;l>=1;--l) {
ls[l][r]=p[l]>r?l:ls[l+1][r];
}
for(int l=n;l;--l) for(int r=l;r<=n;++r) {
if(ls[l][r]<=r) {
int i=ls[l][r];
if(rp[l][r]<i&&f[l][i-1]&&f[i+1][r]) g[l][r]=true;
if(r<cv[l][i-1]&&g[l][i-1]&&f[i+1][r]) f[l][r]=true;
continue;
}
for(int i=l;i<=r;++i) if(i<p[i]) {
if(rp[l][r]<i&&mx[l][i-1]<=p[i]&&f[l][i-1]&&f[i+1][p[i]-1]&&f[p[i]+1][r]) f[l][r]=true,h[l][r]=i;
if(mn[p[i]+1][r]>=i&&cv[l][i-1]>r&&g[l][i-1]&&f[i+1][p[i]-1]&&f[p[i]+1][r]) f[l][r]=true,h[l][r]=n+i;
if(f[l][r]) goto sc;
}
if(mn[l][r]<l) {
int i=p[mn[l][r]];
if(cv[l][i-1]>r&&g[l][i-1]&&f[i+1][r]) f[l][r]=true,h[l][r]=2*n+i;
}
sc:g[l][r]=f[l][r];
}
if(!f[1][n]) return cout<<"No solution.\n",0;
dp(1,n,0,0);
cout<<"Cleared.\n"<<n/2*3<<"\n";
vector <int> c[2];
for(int i=1;i<=n;++i) {
cout<<b[i]+1,c[b[i]].push_back(a[i]);
for(;c[0].size()&&c[1].size()&&c[0].back()==c[1].back();c[0].pop_back(),c[1].pop_back()) cout<<0;
}
cout<<"\n";
return 0;
}
Y. [QOJ7994] 勿蹖宠物 (3)
考虑从中间开始向两侧填字符串来维护回文串,每次转移长度较小的一侧,只需 dp 记录长度较长一侧剩余的字符串是哪个前缀或者哪个后缀。
预处理每个状态加上一个字符串后的转移即可。
时间复杂度 \(\mathcal O(nS^2+nmS)\),其中 \(S=\sum|s_i|\)。
代码:
#include<bits/stdc++.h>
using namespace std;
const int MOD=1e9+7;
int n,m,q,d[355],f[1005][1205],L[335][605],R[335][605],g[1205][355];
string s[355];
void add(int &x,const int &y) { x=(x+y>=MOD)?x+y-MOD:x+y; }
signed main() {
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
cin>>n>>m;
for(int i=1;i<=n;++i) {
cin>>s[i],d[i]=s[i].size();
for(int j=0;j<d[i];++j) L[i][j]=++q,R[i][j]=++q;
}
for(int i=1;i<=n;++i) g[0][i]=R[i][0];
for(int i=1;i<=n;++i) for(int j=0;j<d[i];++j) {
for(int k=1;k<=n;++k) {
int &z=g[L[i][j]][k];
for(int x=0;x<min(j+1,d[k]);++x) if(s[i][j-x]!=s[k][x]) { z=-1; break; }
if(z<0) continue;
if(j+1<d[k]) z=R[k][j+1];
else if(j+1>d[k]) z=L[i][j-d[k]];
else z=0;
}
for(int k=1;k<=n;++k) {
int &z=g[R[i][j]][k];
for(int x=0;x<min(d[i]-j,d[k]);++x) if(s[i][j+x]!=s[k][d[k]-1-x]) { z=-1; break; }
if(z<0) continue;
if(d[i]-j<d[k]) z=L[k][d[k]-(d[i]-j)-1];
else if(d[i]-j>d[k]) z=R[i][j+d[k]];
else z=0;
}
}
if(m%2==0) {
++f[m][0];
for(int i=1;i<=n;++i) if(d[i]<=m) for(int j=0;j+1<d[i];++j) {
for(int x=0;x<min(j+1,d[i]-1-j);++x) if(s[i][j-x]!=s[i][j+1+x]) goto n1;
if(j+1<d[i]-1-j) ++f[m-d[i]][R[i][2*j+2]];
else if(j+1>d[i]-1-j) ++f[m-d[i]][L[i][2*j-d[i]+1]];
else ++f[m-d[i]][0];
n1:;
}
} else {
for(int i=1;i<=n;++i) if(d[i]<=m) for(int j=0;j<d[i];++j) {
for(int x=0;x<min(j,d[i]-1-j);++x) if(s[i][j-1-x]!=s[i][j+1+x]) goto n2;
if(j<d[i]-1-j) ++f[m-d[i]][R[i][2*j+1]];
else if(j>d[i]-1-j) ++f[m-d[i]][L[i][2*j-d[i]]];
else ++f[m-d[i]][0];
n2:;
}
}
for(int i=m;i;--i) for(int j=0;j<=q;++j) if(f[i][j]) {
for(int k=1;k<=n;++k) if(i>=d[k]&&~g[j][k]) add(f[i-d[k]][g[j][k]],f[i][j]);
}
cout<<f[0][0]<<"\n";
return 0;
}
Z. [QOJ6650] Freshman Dream (4)
解方程 \(\sum a_{i,k}b_{k,j}=a_{i,j}b_{i,j}\),容易发现每组 \(j\) 相同的 \(b\) 构成 \(n\) 元 \(n\) 次方程组。
高斯消元之后暴力枚举每个自由元的取值,然后背包。
由于 \(a\) 随机,因此矩阵秩 \(\le n-k\) 的概率为 \(\mathcal O\left(\dfrac 1{k!}\right)\) 级别。
时间复杂度 \(\mathcal O\left(\dfrac{n^4}\omega\right)\)。
代码:
#include<bits/stdc++.h>
using namespace std;
bitset <105> a[105],f[105],o;
vector <bitset<105>> g[105],h;
int dp[105][10005];
signed main() {
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
int n,k; cin>>n>>k;
for(int i=1,c;i<=n;++i) for(int j=1;j<=n;++j) cin>>c,a[i][j]=c;
memset(dp,-1,sizeof(dp)),dp[0][0]=0;
for(int t=1;t<=n;++t) {
for(int i=1;i<=n;++i) f[i].reset();
for(int i=1;i<=n;++i) {
o=a[i],o[i]=o[i]^a[i][t];
for(int j=1;j<=n;++j) if(o[j]) {
if(!f[j][j]) { f[j]=o; break; }
else o^=f[j];
}
}
for(int i=n;i;--i) if(f[i].any()) {
for(int j=i-1;j;--j) if(f[j][i]) f[j]^=f[i];
}
h.clear();
for(int i=n;i;--i) if(f[i].none()) {
for(int j=1;j<=n;++j) o[j]=f[j][i];
o[i]=1,h.push_back(o);
}
for(int s=0,c=h.size();s<(1<<c);++s) {
o.reset();
for(int x=0;x<c;++x) if(s>>x&1) o^=h[x];
g[t].push_back(o); int z=o.count();
for(int i=z;i<=k;++i) if(~dp[t-1][i-z]) dp[t][i]=s;
}
}
if(dp[n][k]<0) return cout<<"-1\n",0;
for(int i=1;i<=n;++i) a[i].reset();
for(int t=n;t;--t) {
int s=dp[t][k];
for(int i=1;i<=n;++i) a[i][t]=g[t][s][i];
k-=g[t][s].count();
}
cout<<"1\n";
for(int i=1;i<=n;++i) for(int j=1;j<=n;++j) cout<<a[i][j]<<" \n"[j==n];
return 0;
}
AA. [QOJ7718] Coconuts (2)
用\(f_{a}\) 表示攻击次数为 \([a_1,a_2,\dots,a_n]\) 时的答案,只用关心 \(a_i\) 的无序集,转移的时候计算一下可能的 \(d\) 序列个数即可。
时间复杂度 \(\mathcal O(\mathrm{Bell}(n)\mathrm{poly}(n))\)。
代码:
#include<bits/stdc++.h>
#include<ext/pb_ds/assoc_container.hpp>
#define ll long long
using namespace std;
int n,a[12],b[12],c[12],d[12],e[12];
int qry() {
memset(d,0,sizeof(d)),memset(e,0,sizeof(e));
for(int i=0;i<n;++i) ++e[a[i]],d[b[i]]+=!c[i];
int s=1;
for(int i=0;i<n;++i) if(c[i]) s*=e[b[i]]--;
if(!s) return 0;
for(int i=0,k=0;i<10;++i) for(k+=d[i];e[i+1]--;s*=k--);
return s;
}
__gnu_pbds::gp_hash_table <ll,int> F;
int dfs(int q) {
int o=qry();
if(!o||!q) return count(c,c+n,1)*o;
for(int i=0;i<n;++i) e[i]=b[i]<<1|c[i];
sort(e,e+n); ll h=0;
for(int i=0;i<n;++i) h=h*22+e[i];
if(F.find(h)!=F.end()) return F[h];
int z=0;
for(int i=0;i<n;++i) if(!c[i]) {
++b[i]; int t=dfs(q-1);
c[i]=1,t+=dfs(q-1),c[i]=0,--b[i],z=max(z,t);
}
return F[h]=z;
}
signed main() {
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
int k; cin>>n>>k;
for(int i=0;i<n;++i) cin>>a[i];
cout<<fixed<<setprecision(20)<<1.*dfs(k)/qry()<<"\n";
return 0;
}
AB. [QOJ8007] Egg Drop Challenge (4.5)
考虑 \(i\to j\) 的转移,设速度为 \(v\),则 \(v\le u_j,v^2\le v_j-2(h_i-h_j)\),分讨 \(v\) 的具体取值,可以写成 \(f_i\gets z+\sqrt{x_i-k}\) 或 \(f_i\gets z-\sqrt{k-x_i}\)。
注意到这样的函数之间只有一个交点,因此可以用类似李超线段树的方式维护最小值,\(v\) 取值的限制可以 CDQ 分治去掉,注意 \(x\) 超出定义域的部分不能插入该函数。
时间复杂度 \(\mathcal O(n\log^3n)\)。
#include<bits/stdc++.h>
#define ll long long
#define ld long double
using namespace std;
const int MAXN=3e5+5;
const ll inf=4e18;
struct info {
ld z; ll k; int o;
inline ld f(ll x) const {
if(o<0) return inf;
if(!o) return x>k?inf:z+sqrtl(k-x);
else return k>x?inf:z-sqrtl(x-k);
}
} e[MAXN];
struct Segt {
int ls[MAXN*20],rs[MAXN*20],tr[MAXN*20],tot,rt;
vector <ll> Z;
void ins(int x,int ul,int ur,int l,int r,int &p) {
if(ul<=l&&r<=ur) {
if(!p) return tr[p=++tot]=x,void();
int mid=(l+r)>>1;
if(e[x].f(Z[mid])<e[tr[p]].f(Z[mid]))swap(tr[p],x);
if(l==r) return ;
if(e[x].f(Z[l])<e[tr[p]].f(Z[l])) ins(x,ul,ur,l,mid,ls[p]);
if(e[x].f(Z[r])<e[tr[p]].f(Z[r])) ins(x,ul,ur,mid+1,r,rs[p]);
return ;
}
if(!p) p=++tot;
int mid=(l+r)>>1;
if(ul<=mid) ins(x,ul,ur,l,mid,ls[p]);
if(mid<ur) ins(x,ul,ur,mid+1,r,rs[p]);
}
ld ask(int x,int l,int r,int p) {
ld z=e[tr[p]].f(Z[x]);
if(!p||l==r) return z;
int mid=(l+r)>>1;
return min(z,x<=mid?ask(x,l,mid,ls[p]):ask(x,mid+1,r,rs[p]));
}
void init(vector<ll>&z) {
for(int i=1;i<=tot;++i) ls[i]=rs[i]=tr[i]=0;
tot=rt=0,Z.swap(z),sort(Z.begin(),Z.end()),Z.erase(unique(Z.begin(),Z.end()),Z.end());
}
void add(int x) {
if(!e[x].o) {
int t=upper_bound(Z.begin(),Z.end(),e[x].k)-Z.begin()-1;
if(t>=0) ins(x,0,t,0,Z.size()-1,rt);
} else {
int t=lower_bound(Z.begin(),Z.end(),e[x].k)-Z.begin();
if(t<(int)Z.size()) ins(x,t,Z.size()-1,0,Z.size()-1,rt);
}
}
ld qry(ll x) {
return ask(lower_bound(Z.begin(),Z.end(),x)-Z.begin(),0,Z.size()-1,rt);
}
} T;
int n,rt;
ll h[MAXN],L[MAXN],R[MAXN];
ld f[MAXN];
array <ll,2> a[MAXN];
void cdq(int l,int r) {
if(l==r) return void();
int mid=(l+r)>>1;
cdq(mid+1,r);
vector <ll> z1,z2;
for(int j=l;j<=mid;++j) a[j]={L[j]*L[j]+2*h[j],j},z1.push_back(2*h[j]),z2.push_back(a[j][0]);
for(int i=mid+1;i<=r;++i) a[i]={R[i]*R[i]+2*h[i],i};
sort(a+l,a+mid+1),sort(a+mid+1,a+r+1);
T.init(z1),rt=0;
for(int j=l,k=mid+1,i;j<=mid;++j) {
for(;k<=r&&a[k][0]<=a[j][0];++k) i=a[k][1],e[i]={f[i]-R[i],a[k][0],0},T.add(i);
i=a[j][1],f[i]=min(f[i],T.qry(2*h[i]));
}
T.init(z2),rt=0;
for(int j=mid,k=r,i;j>=l;--j) {
for(;k>mid&&a[k][0]>a[j][0];--k) i=a[k][1],e[i]={f[i],2*h[i],1},T.add(i);
i=a[j][1],f[i]=min(f[i],T.qry(a[j][0])+L[i]);
}
cdq(l,mid);
}
signed main() {
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
cin>>n,e[0]={0,0,-1};
for(int i=1;i<=n;++i) cin>>h[i]>>R[i]>>L[i];
fill(f+1,f+n,inf),cdq(1,n);
if(f[1]>=inf) cout<<"-1\n";
else cout<<fixed<<setprecision(20)<<f[1]<<"\n";
return 0;
}
AC. [QOJ6656] 先人类的人类选别 (4.5)
考虑一个前缀操作后的变化,如果 \(x\) 小于其中最小值那么无事发生,否则前缀最小值会被弹出。
因此一个前缀 \(a[1,k]\) 操作若干次后剩余的元素就是所有 \(x\) 加上原有元素的前 \(k\) 大。
主席树维护当前所有的 \(x\) 以及每个 \(a[1,k]\),简单二分即可。
时间复杂度 \(\mathcal O((n+q)\log n)\)。
代码:
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=5e5+5;
struct Segt {
ll su[MAXN*40];
int ct[MAXN*40],ls[MAXN*40],rs[MAXN*40],tot;
void ins(int x,int l,int r,int q,int &p) {
ct[p=++tot]=ct[q]+1,su[p]=su[q]+x;
if(l==r) return ;
int mid=(l+r)>>1;
if(x<=mid) ins(x,l,mid,ls[q],ls[p]),rs[p]=rs[q];
else ins(x,mid+1,r,rs[q],rs[p]),ls[p]=ls[q];
}
ll qry(int k,int l,int r,int q,int p) {
if(l==r) return 1ll*k*l;
int mid=(l+r)>>1,w=ct[rs[q]]+ct[rs[p]];
if(k<=w) return qry(k,mid+1,r,rs[q],rs[p]);
return qry(k-w,l,mid,ls[q],ls[p])+su[rs[q]]+su[rs[p]];
}
} T;
int n,m,rt[MAXN],o;
signed main() {
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
cin>>n>>m;
for(int i=1,x;i<=n;++i) cin>>x,T.ins(x,1,n,rt[i-1],rt[i]);
for(int x,l,r;m--;) {
cin>>x>>l>>r,T.ins(x,1,n,o,o);
cout<<T.qry(r,1,n,rt[r],o)-T.qry(l-1,1,n,rt[l-1],o)<<"\n";
}
return 0;
}
AD. [QOJ9691] Little, Cyan, Fish! (3.5)
先把操作线段处理成互不相交的,然后按行扫描线,维护每个元素在三种操作中是否被覆盖容易用 bitset 优化,然后我们要统计每 \(\omega\) 位的贡献,拆成 \(4\) 个大小为 \(\dfrac\omega 4\) 的部分预处理即可。
正解是行列同时分块后 FFT。
时间复杂度 \(\mathcal O\left(\dfrac{n^2+nq}{\omega}\right)\)。
代码:
#include<bits/stdc++.h>
#define ull unsigned long long
using namespace std;
const int MAXN=1e5+5,MAXQ=1675,MOD=998244353,B=60,P=(1<<15)-1;
struct seg { int x,y; };
int n,m,zx[MAXN],zy[MAXN],s[MAXQ*4][P+5];
vector <seg> f[MAXN],g[MAXN],h[MAXN*2],w,ig[MAXN],ih[MAXN];
ull a[MAXQ],b[MAXQ],c[MAXQ],pw[B+5];
void upd(vector<seg>&e) {
sort(e.begin(),e.end(),[&](auto i,auto j){ return i.x<j.x; }),w.clear();
for(auto i:e) {
if(w.empty()||w.back().y<i.x-1) w.push_back(i);
else w.back().y=max(w.back().y,i.y);
}
}
signed main() {
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
cin>>n>>m;
for(int i=1;i<=B;++i) pw[i]=pw[i-1]<<1|1;
for(int i=1;i<=n;++i) cin>>zx[i];
for(int i=1;i<=n;++i) cin>>zy[i];
for(int lx,ly,rx,ry;m--;) {
cin>>lx>>ly>>rx>>ry;
if(lx>rx||(lx==rx&&ly>ry)) swap(lx,rx),swap(ly,ry);
if(lx==rx) f[lx].push_back({ly,ry});
else if(ly==ry) g[ly].push_back({lx,rx});
else h[ly+n-lx].push_back({lx,rx});
}
for(int i=1;i<=n;++i) {
upd(f[i]),f[i].swap(w),upd(g[i]);
for(auto j:w) ig[j.x].push_back({i,1}),ig[j.y+1].push_back({i,0});
}
for(int i=-n;i<=n;++i) {
upd(h[i+n]);
for(auto j:w) ih[j.x].push_back({j.x+i,1}),ih[j.y+1].push_back({j.y+1+i,0});
}
for(int i=0;i<=n/15;++i) {
for(int j=0;j<15;++j) s[i][1<<j]=zy[i*15+j];
for(int j=1;j<=P;++j) s[i][j]=(s[i][j&-j]+s[i][j&(j-1)])%MOD;
}
int q=n/B,ans=0; const ull U=pw[B];
for(int i=1;i<=n;++i) {
memset(a,0,sizeof(a));
for(auto j:f[i]) {
int l=j.x/B,r=j.y/B;
if(l==r) a[l]^=pw[j.y%B+1]^pw[j.x%B];
else {
a[l]^=U^pw[j.x%B],a[r]^=pw[j.y%B+1];
if(r-l>=2) memset(a+l+1,0xff,(r-l-1)<<3);
}
}
for(auto j:ig[i]) {
if(j.y) b[j.x/B]|=1ll<<j.x%B;
else b[j.x/B]&=~(1ll<<j.x%B);
}
for(auto j:ih[i]) {
if(j.y) c[j.x/B]|=1ll<<j.x%B;
else c[j.x/B]&=~(1ll<<j.x%B);
}
ull z=0,d;
for(int x=0;x<=q;++x) if(d=a[x]&b[x]&c[x]) {
z+=0ll+s[x<<2][d&P]+s[x<<2|1][d>>15&P]+s[x<<2|2][d>>30&P]+s[x<<2|3][d>>45&P];
}
ans=(ans+z%MOD*zx[i])%MOD;
for(int x=q;~x;--x) c[x+1]|=c[x]>>59&1,c[x]=c[x]<<1&U;
}
cout<<ans<<"\n";
return 0;
}

浙公网安备 33010602011771号