Diary 2.0
下面内容来源于互联网,如有雷同纯属正常……
CF674G Choosing Ads
下设 \(\lfloor \frac{100}{p}\rfloor=k\)。
我们考虑维护一个可行种类集合 \(S\) 并记录每个种类的出现次数。考虑往集合里添加一个元素。如果该元素的种类在 \(S\) 中出现则出现次数 \(+1\),否则如果集合大小 $< k $ 则直接加入集合,否则将集合里所有种类的出现次数 \(-1\) (同时删除集合里出现次数 \(=0\) 的种类)。
考虑做法的正确性。显然这样集合里每个种类最多被消去 \(\frac{x}{\lfloor \frac{100}{p}\rfloor +1}\) 次,显然 \(\le \lceil\frac{xp}{100}\rceil\) ,即可行种类最小出现次数的。(这里 \(x\) 为元素个数)
考虑如何实现和复杂度。可以直接用线段树维护每个区间的可行集合。由于集合大小不超过 \(k\) ,且由题 \(k \le 5\) ,所以可以暴力 \(O(k^2)\) 合并两个集合,复杂度 \(O(n\log n\times k^2)\),注意常数即可通过。
#include<bits/stdc++.h>
using namespace std;
const int N=1.5e5+5;
namespace io {
const int SIZE=(1<<21)+1;
char ibuf[SIZE],*iS,*iT,c; int x;
#define gc()(iS==iT?(iT=(iS=ibuf)+fread(ibuf,1,SIZE,stdin),(iS==iT?EOF:*iS++)):*iS++)
inline int gi (){
for(c=gc();c<'0'||c>'9';c=gc());
for(x=0;c<='9'&&c>='0';c=gc()) x=(x<<1)+(x<<3)+(c&15); return x;
}
} using io::gi;
int n,q,k,a[N],tg[N<<2];
struct node
{
int w[7],t[7],sze;
void clr()
{
sze=0;
for(int i=0;i<=k;++i) w[i]=t[i]=0;
}
} st[N<<2],nu;
#define lx (x<<1)
#define rx (x<<1|1)
node merge(node u, node v)
{
for(int i=1;i<=v.sze;++i)
{
bool flag=false;
int mn=v.t[i];
for(int j=1;j<=u.sze;++j)
{
if(u.w[j]==v.w[i])
{
u.t[j]+=v.t[i],flag=true;
break;
}
mn=min(mn,u.t[j]);
}
if(flag) continue;
if(u.sze<k)
{
++u.sze,u.w[u.sze]=v.w[i],u.t[u.sze]=v.t[i];
continue;
}
nu.clr();
for(int j=1;j<=u.sze;++j)
if(u.t[j]-=mn) nu.w[++nu.sze]=u.w[j],nu.t[nu.sze]=u.t[j];
if(v.t[i]-=mn) nu.w[++nu.sze]=v.w[i],nu.t[nu.sze]=v.t[i];
swap(u,nu);
}
return u;
}
void cov(int x, int w, int t)
{
st[x].sze=1,st[x].w[1]=w,st[x].t[1]=t;
}
void pushdown(int x, int l, int r)
{
if(!tg[x]) return ;
int mid=l+r>>1;
tg[lx]=tg[rx]=tg[x];
cov(lx,tg[x],mid-l+1),cov(rx,tg[x],r-mid);
tg[x]=0;
}
void build(int x, int l, int r)
{
if(l==r)
{
cov(x,a[l],1);
return ;
}
int mid=l+r>>1;
build(lx,l,mid),build(rx,mid+1,r);
st[x]=merge(st[lx],st[rx]);
}
void update(int x, int l, int r, int sl, int sr, int w)
{
if(sl<=l&&r<=sr)
{
cov(x,w,r-l+1);
tg[x]=w; return ;
}
pushdown(x,l,r);
int mid=l+r>>1;
if(sl<=mid) update(lx,l,mid,sl,sr,w);
if(sr>mid) update(rx,mid+1,r,sl,sr,w);
st[x]=merge(st[lx],st[rx]);
}
node query(int x, int l, int r, int sl, int sr)
{
if(sl<=l&&r<=sr) return st[x];
pushdown(x,l,r);
int mid=l+r>>1;
if(sr<=mid) return query(lx,l,mid,sl,sr);
if(sl>mid) return query(rx,mid+1,r,sl,sr);
return merge(query(lx,l,mid,sl,sr),query(rx,mid+1,r,sl,sr));
}
int main()
{
n=gi(),q=gi(),k=100/gi();
for(int i=1;i<=n;++i) a[i]=gi();
build(1,1,n);
while(q--)
{
int op=gi(),l=gi(),r=gi();
if(op==1) update(1,1,n,l,r,gi());
else
{
node t=query(1,1,n,l,r);
printf("%d",t.sze);
for(int i=1;i<=t.sze;++i) printf(" %d",t.w[i]);
puts("");
}
}
}
AGC030D Inversion Sum
类似ZJOI2019线段树,问题转化成每个操作有 \(\frac{1}{2}\) 的概率执行,求 \(\sum_{i\lt j} P(A_i>A_j)\) 。设 \(f(i,j)=P(A_i\gt A_j)\),每次交换 \(A_x,A_y\) 显然只对 \(i=x\) 或 \(j=y\) 的 \(f(i,j)\) 有影响,直接修改即可。复杂度 \(O(n^2)\) .
#include<bits/stdc++.h>
using namespace std;
const int N=3005,Mod=1e9+7,iM=Mod+1>>1;
int n,q,a[N],f[N][N],t[N][N],ans,p=1;
#define mul(x,y) (1ll*(x)*(y)%Mod)
inline int add(int x, int y)
{ return (x+y>=Mod?x+y-Mod:x+y);
}
int main()
{
scanf("%d%d",&n,&q);
for(int i=1;i<=n;++i) scanf("%d",&a[i]);
for(int i=1;i<=n;++i)
for(int j=1;j<=n;++j) f[i][j]=(a[i]>a[j]);
while(q--)
{
int x,y;
scanf("%d%d",&x,&y);
if(x>y) swap(x,y);
if(x==y) continue;
t[x][y]=mul(iM,add(f[x][y],f[y][x]));
for(int i=1;i<=n;++i) if(i!=x&&i!=y)
t[x][i]=mul(iM,add(f[x][i],f[y][i])),
t[i][x]=mul(iM,add(f[i][x],f[i][y])),
t[y][i]=mul(iM,add(f[y][i],f[x][i])),
t[i][y]=mul(iM,add(f[i][y],f[i][x]));
f[x][y]=f[y][x]=t[x][y];
for(int i=1;i<=n;++i) if(i!=x&&i!=y)
f[x][i]=t[x][i],f[i][x]=t[i][x],f[y][i]=t[y][i],f[i][y]=t[i][y];
p=mul(2,p);
}
for(int i=1;i<=n;++i)
for(int j=i+1;j<=n;++j) ans=add(ans,f[i][j]);
printf("%d\n",mul(ans,p));
}
AGC024F Simple Subsequence Problem
考虑序列自动机匹配子序列的过程:将主串的指针向后移动到第一个与当前模式串指针指向字符相同的字符,然后将模式串指针后移一位。显然这样的匹配过程是唯一的。
于是我们可以模拟这样的匹配过程来生成一个子序列:我们设计一个新的自动机,设 \(f(A,B)\) 为自动机的状态,表示当前匹配生成的模式串为 \(A\),主串剩余的串为 \(B\),转移可以枚举 \(A\) 的下一个字符。例如:
显然 \(f(X,\empty)\) 是终止态。而且因为序列自动机匹配过程唯一,所以两点之间的路径也是唯一的。
于是本题的问题就转化成:给定自动机的若干个起点,求到每个 \(f(X,\empty)\) 的路径条数。直接在自动机上 DP 即可。
因为 \(|A|+|B|\le N\),所以自动机状态数为 \(2^N \times N\) 级别的,故时间复杂度为 \(O(2^N \times N)\).
可以选择你喜欢的方式压状态,可能需要一点处理二进制的技巧。
#include<bits/stdc++.h>
using namespace std;
const int N=21;
vector<int> f[N][N];
//f[i][j][S] 主串长 i, 模式串长 j, 主串+模式串=S
int n,K; char s[1<<N];
int main()
{
scanf("%d%d",&n,&K);
for(int i=0;i<=n;++i)
{
scanf("%s",s);
for(int j=0;i+j<=n;++j) f[i][j].resize(1<<i+j);
for(int j=0;s[j];++j) f[i][0][j]=(s[j]-'0');
}
for(int i=n;i;--i)
for(int j=0;i+j<=n;++j)
for(int k=0;k<(1<<i+j);++k) if(f[i][j][k])
{
int a=k>>j, b=(j?k&((1<<j)-1):0);
//这里a,b与上文相反。
f[0][j][b]+=f[i][j][k];
if(a)
{
int p=32-__builtin_clz(a),nb=j?(b<<1|1):1;
f[i-(i-p+1)][j+1][(a&((1<<p-1)-1))<<(j+1)|nb]+=f[i][j][k];
}
if(a!=(1<<i)-1)
{
unsigned tmp=(unsigned)a<<(32-i);
int p=i-__builtin_clz(~tmp),nb=j?(b<<1):0;
f[i-(i-p+1)][j+1][(a&((1<<p-1)-1))<<(j+1)|nb]+=f[i][j][k];
}
}
for(int i=n;i;--i)
for(int k=0;k<(1<<i);++k) if(f[0][i][k]>=K)
{
for(int j=i-1;~j;--j) putchar((k>>j&1)+'0');
return 0;
}
}
CF573E Bear and Bowling
本题可以考虑这样一个贪心策略:每次选取对答案贡献最大的点加入,直到最大贡献为负。
事实上这个策略是正确的,具体证明内容转这篇博客。
通过这个结论可以得到一个重要推论:
- 设 \(S_i\) 为选择集合大小为 \(i\) 的一个最优解集合,则存在 \(S_i\sub S_{i+1}\)。
这个推论可以帮助我们得到一个更优复杂度的做法。
我们考虑一个 \(n^2\) 的 DP:设 \(f_{i,j}\) 表示前 \(i\) 个元素,选了 \(j\) 个元素的答案:
由上面的推论可以得到:选择 \(a_i\) 的 \(j\) 一定是一段后缀。
(事实上这个结论有一个牛逼的证明方法,可以看这篇博客。)
所以上面的 DP 是一个分段函数,我们可以用平衡树维护差分数组,每次平衡树上二分找到分界点插入,然后后缀加即可。
复杂度 \(O(n\log n)\)。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
inline int gi()
{
char c=getchar(); int x=0,f=1;
for(;c<'0'||c>'9';c=getchar())if(c=='-')f=-1;
for(;c>='0'&&c<='9';c=getchar())x=(x<<1)+(x<<3)+c-'0';
return x*f;
}
const int N=1e5+5;
int ch[N][2],sze[N],fa[N],n,rt,cnt;
ll val[N],tag[N],sum,ans;
void pushup(int x)
{
sze[x]=sze[ch[x][0]]+sze[ch[x][1]]+1;
}
void pushdown(int x)
{
if(!tag[x]||!x) return ;
tag[ch[x][0]]+=tag[x],tag[ch[x][1]]+=tag[x];
val[ch[x][0]]+=tag[x],val[ch[x][1]]+=tag[x];
tag[x]=0;
}
void rotate(int x)
{
int y=fa[x],z=fa[y];
int k=ch[y][1]==x,w=ch[x][!k];
ch[z][ch[z][1]==y]=x,ch[x][!k]=y,ch[y][k]=w;
fa[w]=y,fa[y]=x,fa[x]=z;
pushup(y);
}
void maintain(int x)
{
if(fa[x]) maintain(fa[x]);
pushdown(x);
}
void splay(int x, int k)
{
maintain(x);
while(fa[x]!=k)
{
int y=fa[x],z=fa[y];
if(z!=k) (ch[z][1]==y)^(ch[y][1]==x)?rotate(x):rotate(y);
rotate(x);
}
pushup(x);
if(!k) rt=x;
}
void insert(int& u, int f, int w, int rk)
{
if(!u)
{
u=++cnt;
sze[u]=1,fa[u]=f;
val[u]=1ll*w*(rk+1);
splay(u,0);
return ;
}
pushdown(u);
int now=rk+sze[ch[u][0]]+1;
if(val[u]<1ll*w*now) insert(ch[u][0],u,w,rk);
else insert(ch[u][1],u,w,now);
}
void dfs(int u)
{
if(!u) return ;
pushdown(u);
dfs(ch[u][0]);
sum+=val[u],ans=max(ans,sum);
dfs(ch[u][1]);
}
int main()
{
n=gi();
for(int i=1;i<=n;++i)
{
int w=gi();
insert(rt,0,w,0);
val[ch[rt][1]]+=w,tag[ch[rt][1]]+=w;
}
dfs(rt),printf("%lld\n",ans);
}
CF611H New Year and Forgotten Tree
显然,相同位数的点之间的边可以直接串成一条链。所以我们只需考虑位数不同的点之间的边。
我们每种位数都选出一个关键点,让关键点连成一棵树,然后让其他所有的边都挂在关键点上。显然这是一种绝对可行的方案。对于一条连接位数为 \(x\) 的点和位数为 \(y\) 的点的一条边,可以选择将 \(x\) 的一个点连向 \(y\) 的关键点或者反之。这就相当于一条边 \((x,y)\) 可以匹配 \(x\) 或 \(y\) 的一个点(即相当于确定了该点的父亲)。
于是问题转化成了二分图匹配问题,可以用 Dinic 判断可行和构造方案。注意到关键点连成的树对边数有影响,由于位数很小(最大为 \(6\)),故可以直接爆搜。时间复杂度懒得分析了。
#include<bits/stdc++.h>
using namespace std;
const int N=2e5+5,M=8,S=1005,inf=1<<30;
int head[S],nxt[S],to[S],wei[S],lev[S],tot,s,t;
void addedge(int u, int v, int w=inf)
{
nxt[++tot]=head[u],head[u]=tot,to[tot]=v,wei[tot]=w;
nxt[++tot]=head[v],head[v]=tot,to[tot]=u,wei[tot]=0;
}
bool bfs()
{
queue<int> q;
memset(lev,-1,sizeof(lev));
lev[s]=0,q.push(s);
while(!q.empty())
{
int u=q.front(); q.pop();
for(int e=head[u];e;e=nxt[e])
if(wei[e]&&lev[to[e]]==-1)
{
lev[to[e]]=lev[u]+1;
if(to[e]==t) return true;
q.push(to[e]);
}
}
return false;
}
int dfs(int u, int mx)
{
if(u==t) return mx;
int l=mx;
for(int e=head[u];e&&l;e=nxt[e])
if(wei[e]>0&&lev[to[e]]==lev[u]+1)
{
int f=dfs(to[e],min(l,wei[e]));
if(!f) lev[to[e]]=-1;
l-=f,wei[e]-=f,wei[e^1]+=f;
}
return mx-l;
}
int n,m,num[N],u[N],v[N],au[N],av[N],b[M],tnow[M],now[M],te[M][M],e[M][M],pur[M],id[M][M],kid[M][M],v1[M],l[M][M];
char s1[M],s2[M]; bool v2[M];
void construct()
{
memcpy(now,tnow,sizeof(tnow));
for(int i=1;i<n;++i) if(u[i]!=v[i])
{
if(l[u[i]][v[i]])
{
au[i]=b[u[i]],av[i]=b[v[i]];
l[u[i]][v[i]]=l[v[i]][u[i]]=false;
}
else if(wei[kid[u[i]][v[i]]])
--wei[kid[u[i]][v[i]]],au[i]=++now[u[i]],av[i]=b[v[i]];
else if(wei[kid[v[i]][u[i]]])
--wei[kid[v[i]][u[i]]],au[i]=b[u[i]],av[i]=++now[v[i]];
else return ;
}
for(int i=1;i<n;++i) if(u[i]==v[i])
au[i]=now[u[i]],av[i]=now[u[i]]+1,now[u[i]]++;
for(int i=1;i<n;++i) if(au[i]>n||av[i]>n) return ;
for(int i=1;i<n;++i) printf("%d %d\n",au[i],av[i]);
exit(0);
}
void calc()
{
memset(head,0,sizeof(head)),tot=1;
int lef=0;
for(int i=1;i<=m;++i)
for(int j=i+1;j<=m;++j)
{
if(e[i][j]<0) return ;
addedge(s,id[i][j],e[i][j]),lef+=e[i][j];
addedge(id[i][j],i),kid[i][j]=tot;
addedge(id[i][j],j),kid[j][i]=tot;
}
for(int i=1;i<=m;++i) addedge(i,t,num[i]-1);
while(bfs()) lef-=dfs(s,inf);
if(!lef) construct();
}
void resume()
{
memcpy(e,te,sizeof(te));
memset(l,false,sizeof(l));
for(int i=1;i<=m;++i) v1[i]=v2[i]=0;
for(int i=1;i<=m-2;++i) ++v1[pur[i]];
for(int i=1;i<=m-2;++i)
for(int j=1;j<=m;++j) if(!v1[j]&&!v2[j])
{
l[pur[i]][j]=l[j][pur[i]]=true;
--e[pur[i]][j],--e[j][pur[i]];
--v1[pur[i]],v2[j]=true;
break;
}
for(int i=1,t=0;i<=m;++i) if(!v2[i])
if(!t) t=i;
else
{
l[i][t]=l[t][i]=true;
--e[i][t],--e[t][i];
break;
}
}
void dfs(int u)
{
if(u>m-2)
{
resume(),calc();
return ;
}
for(int i=1;i<=m;++i) pur[u]=i,dfs(u+1);
}
int main()
{
scanf("%d",&n);
for(int i=1,j=0,k=1;i<=n;++i)
{
if(i==k) ++j,tnow[j]=b[j]=k,k*=10;
++num[j];
}
for(int i=1;i<n;++i)
{
scanf("%s%s",s1,s2);
int l1=strlen(s1),l2=strlen(s2);
m=max(m,max(l1,l2));
if(l1==l2) --num[l1];
else ++te[l1][l2],++te[l2][l1];
u[i]=l1,v[i]=l2;
}
for(int i=1;i<=m;++i) if(!num[i])
{
puts("-1"); return 0;
}
s=m*(m-1)/2+m+1,t=s+1;
int tid=m;
for(int i=1;i<=m;++i)
for(int j=i+1;j<=m;++j) id[i][j]=++tid;
dfs(1); puts("-1");
}
CF626G Raffles
(这个题写题解感觉有点说不清楚,或许是我理解不到位,可以去看看作业题解或其他人的博客)
观察到每个奖池的贡献增量关于奖池的彩票数单减,据此我们可以得到一个贪心做法:每次选贡献最大的奖池放入彩票即可,因为每次的选择是无后效性的。使用堆维护即可。
根据这个结论也可以得到一些实现上的技巧,可以避免讨论限制的问题。具体见代码。
接下来考虑修改。添加一张彩票,你可以从中拿出一张放到更优的奖池里;相应的,拿走一张彩票,你可以从其他地方拿一张彩票到这个奖池来。显然这样的操作可能会使得操作更优。事实上,我们也无法用更多的操作使得局面更优(可以根据上面的观察手玩,这里不再证明)。
事实上,两种操作都可以简化为:删除一个减量最小的彩票,再添加一个增量最大的彩票。故我们要支持:单点插入/修改、查询或删除最值。使用线段树或可删堆维护即可。复杂度 \(O(n\log n)\).
#include<bits/stdc++.h>
using namespace std;
const int N=2e5+5;
inline int gi()
{
char c=getchar(); int x=0;
for(;c<'0'||c>'9';c=getchar());
for(;c>='0'&&c<='9';c=getchar())x=(x<<1)+(x<<3)+c-'0';
return x;
}
int n,t,q,p[N],l[N],f[N];
inline double P(int x, int y)
{
if(x==-1) return -1e18;
return (double)x/(x+y);
}
inline double addval(int x)
{
if(f[x]>=l[x]) return 0;
return (P(f[x]+1,l[x])-P(f[x],l[x]))*p[x];
}
inline double delval(int x)
{
if(f[x]>l[x]) return 0;
return (P(f[x]-1,l[x])-P(f[x],l[x]))*p[x];
}
struct node
{
double w;
int id;
bool operator < (const node x) const
{ return w==x.w?id<x.id:w<x.w;
}
bool operator == (const node x) const
{ return w==x.w&&id==x.id;
}
};
struct heap
{
priority_queue<node> x,y,dx,dy;
void pop(int id)
{
dx.push((node){addval(id),id}),
dy.push((node){delval(id),id});
}
int add_top()
{
while(!dx.empty()&&x.top()==dx.top()) x.pop(),dx.pop();
return x.top().id;
}
int del_top()
{
while(!dy.empty()&&y.top()==dy.top()) y.pop(),dy.pop();
return y.top().id;
}
void push(int id)
{
x.push((node){addval(id),id}),
y.push((node){delval(id),id});
}
} Q;
double ans;
void add()
{
int x=Q.add_top();
ans+=addval(x),Q.pop(x);
++f[x],Q.push(x);
}
void del()
{
int x=Q.del_top();
ans+=delval(x),Q.pop(x);
--f[x],Q.push(x);
}
int main()
{
n=gi(),t=gi(),q=gi();
for(int i=1;i<=n;++i) p[i]=gi();
for(int i=1;i<=n;++i) l[i]=gi(),Q.push(i);
while(t--) add();
while(q--)
{
int o=gi(),x=gi();
if(o==1)
{
if(f[x]<=l[x]) ans+=(P(f[x],l[x]+1)-P(f[x],l[x]))*p[x];
Q.pop(x),++l[x],Q.push(x);
}
if(o==2)
{
if(f[x]<l[x]) ans+=(P(f[x],l[x]-1)-P(f[x],l[x]))*p[x];
Q.pop(x),--l[x],Q.push(x);
}
del(),add(),printf("%.9lf\n",ans);
}
}
CF605E Intergalaxy Trips
设 \(f_u\) 表示 \(u\) 到 \(n\) 的期望时间。对于当前的点 \(u\),我们一定是尝试移动到相邻的期望时间比他少的最优的点。如果没有这样的点,我们一定是选择不移动。
我们可以倒着考虑,类似 Dijkstra 算法,我们每次找到期望时间最短的点去更新周围连向该点的点的期望时间。
对于每个点的递推方程:
按照上面所说的,倒着做,找到期望时间最小的 \(x\) 去更新连向该点的 \(u\) 即可。复杂度 \(O(N^2)\).
#include<bits/stdc++.h>
using namespace std;
const int N=1005;
double p[N][N],f[N],g[N];
bool vis[N];
int main()
{
int n; scanf("%d",&n);
for(int i=1;i<=n;++i)
for(int j=1,x;j<=n;++j)
scanf("%d",&x),p[i][j]=(double)x/100.0;
for(int i=1;i<n;++i) f[i]=g[i]=1;
while(true)
{
int x=0; double now;
for(int i=1;i<=n;++i)
if(!vis[i]&&(!x||f[i]<now*(1-g[i]))) x=i,now=f[i]/(1-g[i]);
if(x==1) return !printf("%.15lf\n",now);
vis[x]=true;
for(int i=1;i<=n;++i) if(!vis[i])
{
f[i]+=now*p[i][x]*g[i];
g[i]*=1-p[i][x];
}
}
}
AGC031E Snuke the Phantom Thief
考虑枚举选择的个数 \(K\),即让 \(K\) 个点去匹配给定的 \(N\) 个数。对于一维的情况,我们考虑选择的数按坐标排序,坐标 \(\le a_i\) 最多选 \(b_i\) 个相当于后 \(K-b_i\) 个数的坐标 \(\gt a_i\) ;坐标 \(\le a_i\) 最多选 \(b_i\) 个相当于前 \(K-b_i\) 个数的坐标 \(\lt a_i\) 。这就相当于每个点对匹配的数的坐标有一个限制。
考虑用费用流拆点完成两维的匹配。具体的,我们将 \(K\) 个点和 \(N\) 个点都拆点,拆成的 \(2K\) 个点分别表示按 \(x\) 排序和按 \(y\) 排序的点,分别连原点和汇点。中间两排 \(N\) 个点对应的点连费用为选择该点的价值的边。然后左边按 \(x\) 的限制,右边按 \(y\) 的限制分别连匹配边即可。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=505,M=2e5+5,inf=1<<30;
int head[N],nxt[M],to[M],wei[M],tot=1,s,t,n,m,x[N],y[N],a[N],b[N],lx[N],rx[N],ly[N],ry[N];
ll cost[M],dis[N],v[N],res,ans; char op[N][2]; bool vis[N];
void adde(int u, int v, ll c=0, int w=1)
{
nxt[++tot]=head[u],head[u]=tot,to[tot]=v,wei[tot]=w,cost[tot]=-c;
nxt[++tot]=head[v],head[v]=tot,to[tot]=u,wei[tot]=0,cost[tot]=c;
}
bool spfa()
{
memset(vis,0,sizeof(bool)*(t+3));
memset(dis,0x3f,sizeof(ll)*(t+3));
queue<int> q;
dis[t]=0,q.push(t);
while(!q.empty())
{
int u=q.front();
vis[u]=false,q.pop();
for(int e=head[u];e;e=nxt[e])
if(wei[e^1]&&dis[to[e]]>dis[u]-cost[e])
{
dis[to[e]]=dis[u]-cost[e];
if(!vis[to[e]]) vis[to[e]]=true,q.push(to[e]);
}
}
return dis[s]!=dis[0];
}
int dfs(int u, int mx)
{
vis[u]=true;
if(u==t) return mx;
int l=mx;
for(int e=head[u];e&&l;e=nxt[e])
if(!vis[to[e]]&&wei[e]>0&&dis[to[e]]==dis[u]-cost[e])
{
int f=dfs(to[e],min(l,wei[e]));
res+=cost[e]*f;
l-=f,wei[e]-=f,wei[e^1]+=f;
}
return mx-l;
}
ll exec()
{
res=0;
while(spfa())
do
{
memset(vis,0,sizeof(bool)*(t+3));
dfs(s,inf);
} while(vis[t]);
return -res;
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;++i) scanf("%d%d%lld",&x[i],&y[i],&v[i]);
scanf("%d",&m);
for(int i=1;i<=m;++i) scanf("%s%d%d",op[i],&a[i],&b[i]);
for(int k=1;k<=n;++k)
{
memset(head,0,sizeof(head)),tot=1;
s=(n+k)*2+1,t=s+1;
for(int i=1;i<=k;++i) adde(s,i),adde(i+k+2*n,t),lx[i]=ly[i]=0,rx[i]=ry[i]=inf;
for(int i=1;i<=n;++i) adde(i+k,i+n+k,v[i]);
for(int i=1;i<=m;++i)
{
if(op[i][0]=='L')
for(int j=b[i]+1;j<=k;++j) lx[j]=max(lx[j],a[i]+1);
if(op[i][0]=='R')
for(int j=1;j<=k-b[i];++j) rx[j]=min(rx[j],a[i]-1);
if(op[i][0]=='D')
for(int j=b[i]+1;j<=k;++j) ly[j]=max(ly[j],a[i]+1);
if(op[i][0]=='U')
for(int j=1;j<=k-b[i];++j) ry[j]=min(ry[j],a[i]-1);
}
for(int i=1;i<=k;++i)
for(int j=1;j<=n;++j)
{
if(lx[i]<=x[j]&&x[j]<=rx[i]) adde(i,j+k);
if(ly[i]<=y[j]&&y[j]<=ry[i]) adde(j+k+n,i+k+2*n);
}
ans=max(ans,exec());
}
printf("%lld\n",ans);
}
AGC028D Chords
题意可以抽象成数轴上有若干个区间,如果两个区间相交但不包含则属于一个连通块。
我们先预处理出 \(s_{l,r}\) 表示 \([l,r]\) 中有多少个点开始时没被钦定,\(g_x\) 表示将 \(x\) 个点两两匹配的方案数,显然有 \(g_x=(x-1)\times g_{x-2}\)。
我们考虑计算每个区间 \([i,j]\) 作为极大连通块的贡献。设 \(f_{i,j}\) 表示只考虑 \([i,j]\) 内的点,\([i,j]\) 作为极大连通块的方案数。
考虑连通图计数的套路,我们使用容斥,用任意图方案数减掉 \(i\) 和 \(j\) 不连通的方案数,则有:
则 \([i,j]\) 作为极大连通块的贡献为 \(f_{i,j}\times g(s_{1,2N}-s_{i,j})\) 。DP 计算所有 \(i,j\) 的贡献即可,复杂度 \(O(N^3)\) 。DP 前注意先判断 \(i,j\) 能不能作为一个极大连通块。
#include<bits/stdc++.h>
using namespace std;
const int N=605,Mod=1e9+7;
int n,k,s[N],f[N][N],g[N],t[N],ans;
#define mul(x,y) (1ll*(x)*(y)%Mod)
inline int add(int x, int y)
{ return (x+y>=Mod?x+y-Mod:x+y);
}
inline int sub(int x, int y)
{ return (x-y<0?x-y+Mod:x-y);
}
bool check(int l, int r)
{
for(int i=l;i<=r;++i)
if(t[i]&&(t[i]<l||t[i]>r)) return false;
return true;
}
int main()
{
scanf("%d%d",&n,&k),n<<=1;
for(int i=1,a,b;i<=k;++i)
{
scanf("%d%d",&a,&b);
t[a]=b,t[b]=a,--s[a],--s[b];
}
for(int i=1;i<=n;++i) s[i]+=s[i-1]+1;
g[0]=1; for(int i=2;i<=n;++i) g[i]=mul(i-1,g[i-2]);
for(int i=1;i<=n;++i)
for(int j=i+1;j<=n;j+=2)
{
if(!g[s[j]-s[i-1]]||!check(i,j)) continue;
f[i][j]=g[s[j]-s[i-1]];
for(int k=i;k<j;++k) f[i][j]=sub(f[i][j],mul(f[i][k],g[s[j]-s[k]]));
ans=add(ans,mul(f[i][j],g[s[n]-(s[j]-s[i-1])]));
}
printf("%d\n",ans);
}
CF536D Tavas in Kansas
预处理最短路。问题可以抽象成:有一个坐标系,一个黑点在原点,先手可以将这个黑点向 \(x\) 轴正方向移动若干步,后手可以向 \(y\) 移,每人移动后后获得黑点与原点为对角线围成的矩形新覆盖的点的分数。
考虑这类博弈的经典 DP:设 \(f_{x,y,0/1}\) 表示黑点坐标为 \((x,y)\),当前为先/后手操作,先后手分差的最大值。倒着 DP,枚举上一步是谁操作的,转移显然。复杂度 \(O(N^2)\) .
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<ll,int> pr;
inline int gi()
{
char c=getchar(); int x=0,f=1;
for(;c<'0'||c>'9';c=getchar())if(c=='-')f=-1;
for(;c>='0'&&c<='9';c=getchar())x=(x<<1)+(x<<3)+c-'0';
return x*f;
}
const int N=2020;
struct edge
{
int v,w;
}; vector<edge> e[N];
ll dis[2][N],qd[N],sum[N][N],f[N][N],g[N][N];
int n,m,x,s,t,p[N],ds[N],dt[N],cnt[N][N];
void dijkstra(int u, ll* dis, int* rd)
{
priority_queue<pr,vector<pr>,greater<pr>> q;
for(int i=1;i<=n;++i) dis[i]=1ll<<60;
dis[u]=0,q.push(pr(0,u));
while(!q.empty())
{
ll w=q.top().first; int u=q.top().second; q.pop();
if(dis[u]!=w) continue;
for(auto x:e[u]) if(dis[x.v]>dis[u]+x.w)
dis[x.v]=dis[u]+x.w,q.push(pr(dis[x.v],x.v));
}
for(int i=1;i<=n;++i) qd[i]=dis[i];
sort(qd+1,qd+1+n),x=unique(qd+1,qd+1+n)-qd-1;
for(int i=1;i<=n;++i) rd[i]=lower_bound(qd+1,qd+1+x,dis[i])-qd;
}
int main()
{
n=gi(),m=gi(),s=gi(),t=gi();
for(int i=1;i<=n;++i) p[i]=gi();
for(int i=1;i<=m;++i)
{
int u=gi(),v=gi(),w=gi();
e[u].push_back((edge){v,w});
e[v].push_back((edge){u,w});
}
dijkstra(s,dis[0],ds),dijkstra(t,dis[1],dt);
for(int i=1;i<=n;++i)
sum[ds[i]][dt[i]]+=p[i],++cnt[ds[i]][dt[i]];
for(int i=n;i;--i)
for(int j=n;j;--j)
sum[i][j]+=sum[i+1][j]+sum[i][j+1]-sum[i+1][j+1],
cnt[i][j]+=cnt[i+1][j]+cnt[i][j+1]-cnt[i+1][j+1];
for(int i=n;i;--i)
for(int j=n;j;--j)
{
if(cnt[i][j]==cnt[i+1][j]) f[i][j]=f[i+1][j];
else f[i][j]=max(f[i+1][j],g[i+1][j])+sum[i][j]-sum[i+1][j];
if(cnt[i][j]==cnt[i][j+1]) g[i][j]=g[i][j+1];
else g[i][j]=min(f[i][j+1],g[i][j+1])-sum[i][j]+sum[i][j+1];
}
if(f[1][1]>0) puts("Break a heart");
if(f[1][1]<0) puts("Cry");
if(!f[1][1]) puts("Flowers");
}
CF553E Kyoya and Train
设 \(f_{i,u}\) 表示在 \(u\) 点,时刻为 \(i\) ,到终点所需的期望钱数。则对一般情况有转移:
对于 \(i \ge t\) 的情况,直接走最短路即可。
观察到转移式是卷积形式,分治 FFT 优化转移即可。复杂度 \(O(mt\log^2 t)\) .
#include<bits/stdc++.h>
using namespace std;
inline int gi()
{
char c=getchar(); int x=0;
for(;c<'0'||c>'9';c=getchar());
for(;c>='0'&&c<='9';c=getchar())x=(x<<1)+(x<<3)+c-'0';
return x;
}
const int S=1e5+5;
const double pi=acos(-1.0);
struct cop
{
double r,i;
cop (double _r=0.0, double _i=0.0) {r=_r, i=_i;}
cop operator + (cop x) const {return cop(r+x.r,i+x.i);}
cop operator - (cop x) const {return cop(r-x.r,i-x.i);}
cop operator * (cop x) const {return cop(r*x.r-i*x.i,r*x.i+i*x.r);}
} a[S],b[S];
namespace FFT
{
int m,r[S],l;
void init(int x)
{
for(m=1,l=0;m<=x;++l,m<<=1);
for(int i=0;i<m;++i)r[i]=(r[i>>1]>>1)|((i&1)<<(l-1));
}
void fft(cop* a, int f)
{
for(int i=0;i<m;++i) if(i<r[i]) std::swap(a[i],a[r[i]]);
for(int i=1;i<m;i<<=1)
{
const cop wn(cos(pi/i),sin(pi/i)*f);
for(int j=0;j<m;j+=(i<<1))
{
cop w(1,0);
for(int k=0;k<i;++k,w=w*wn)
{
cop v1=a[j+k],v2=w*a[i+j+k];
a[j+k]=v1+v2,a[i+j+k]=v1-v2;
}
}
}
if(f==-1) for(int i=0;i<m;i++) a[i].r/=m;
}
void mult()
{
fft(a,1),fft(b,1);
for(int i=0;i<m;++i) a[i]=a[i]*b[i];
fft(a,-1);
}
}
const int N=105,M=20005;
int n,m,t,x,u[N],v[N],w[N],d[N][N];
double p[N][M],s[N][M],f[N][M],g[N][M];
void cdq(int l, int r)
{
if(l==r)
{
for(int i=1;i<n;++i) f[i][l]=1e20;
for(int i=1;i<=m;++i)
f[u[i]][l]=min(f[u[i]][l],w[i]+g[i][l]+s[i][t-l+1]*(d[v[i]][n]+x));
return ;
}
int mid=l+r>>1;
cdq(mid+1,r);
FFT::init(r-l+1);
for(int x=1;x<=m;++x)
{
for(int i=0;i<FFT::m;++i) a[i].r=a[i].i=b[i].r=b[i].i=0;
for(int i=mid+1;i<=r;++i) a[i-mid-1].r=f[v[x]][i];
for(int i=1;i<=r-l;++i) b[r-l-i].r=p[x][i];
FFT::mult();
for(int i=l;i<=mid;++i) g[x][i]+=a[i-mid-1+r-l].r;
}
cdq(l,mid);
}
int main()
{
n=gi(),m=gi(),t=gi(),x=gi();
for(int i=1;i<=n;++i)
for(int j=1;j<=n;++j) if(i!=j) d[i][j]=1e9;
for(int i=1;i<=m;++i)
{
u[i]=gi(),v[i]=gi(),w[i]=gi(),d[u[i]][v[i]]=w[i];
for(int j=1;j<=t;++j) p[i][j]=(double)gi()/100000;
for(int j=t;j;--j) s[i][j]=s[i][j+1]+p[i][j];
}
for(int k=1;k<=n;++k)
for(int i=1;i<=n;++i) if(d[i][k]!=1e9)
for(int j=1;j<=n;++j) if(d[k][j]!=1e9)
d[i][j]=min(d[i][j],d[i][k]+d[k][j]);
cdq(0,t),printf("%.10lf\n",f[1][0]);
}
CF517D Campus
离线,类似 Kruskal 重构树,合并两个集合时新建一个虚点连接合并的两个点,构建出两个森林。这样每个操作相当于对一个子树进行操作。线段树二分求出每个询问上一个清空操作的时间,然后树状数组求一段时间区间 A
操作的贡献和即可。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
inline int gi()
{
char c=getchar(); int x=0;
for(;c<'0'||c>'9';c=getchar());
for(;c>='0'&&c<='9';c=getchar())x=(x<<1)+(x<<3)+c-'0';
return x;
}
const int N=1e6+5;
int st[N<<2],tg[N<<2],n,m,mn[N];
int ed[N],res;
bool gans; char op[N][2];
vector<pair<int,int>> qa[N];
vector<int> qq[N],qz[N];
ll ans[N],t[N];
void upd(int i, int w)
{
for(;i<=n+m;i+=(i&-i)) t[i]+=w;
}
ll qry(int i)
{
ll r=0;
for(;i;i-=(i&-i)) r+=t[i];
return r;
}
#define lx (x<<1)
#define rx (x<<1|1)
void update(int x, int l, int r, int s, int w)
{
if(l==r)
{
st[x]+=w;
return ;
}
int mid=l+r>>1;
s<=mid?update(lx,l,mid,s,w):update(rx,mid+1,r,s,w);
st[x]=st[lx]+st[rx];
}
void search(int x, int l, int r)
{
if(l==r)
{
res=l;
return ;
}
int mid=l+r>>1;
st[rx]?search(rx,mid+1,r):search(lx,l,mid);
}
void query(int x, int l, int r, int sr)
{
if(gans) return ;
if(r<=sr)
{
if(st[x]) gans=true,search(x,l,r);
return ;
}
int mid=l+r>>1;
if(sr>mid) query(rx,mid+1,r,sr);
query(lx,l,mid,sr);
}
struct Tree
{
vector<int> e[N];
int f[N],sze[N],id;
void init()
{
id=n;
for(int i=1;i<=n+m;++i) f[i]=i,sze[i]=1;
}
int fset(int x)
{
if(f[x]==x) return x;
return f[x]=fset(f[x]);
}
void addp(int x, int y)
{
++id; x=fset(x),y=fset(y);
e[id].push_back(x),e[id].push_back(y);
f[x]=f[y]=id,sze[id]=sze[x]+sze[y];
}
} t1,t2;
void dfs2(int u)
{
for(auto x:qz[u]) update(1,1,m,x,1);
for(auto x:qq[u]) gans=false,res=0,query(1,1,m,x),ed[x]=res;
for(auto v:t2.e[u]) dfs2(v);
for(auto x:qz[u]) update(1,1,m,x,-1);
}
void dfs1(int u)
{
for(auto x:qa[u]) upd(x.second,x.first);
for(auto x:qq[u]) ans[x]+=qry(x)-qry(ed[x]);
for(auto v:t1.e[u]) dfs1(v);
for(auto x:qa[u]) upd(x.second,-x.first);
}
int main()
{
n=gi(),m=gi();
t1.init(),t2.init();
for(int i=1;i<=m;++i)
{
scanf("%s",op[i]); char c=op[i][0];
if(c=='U') t1.addp(gi(),gi());
if(c=='M') t2.addp(gi(),gi());
if(c=='A')
{
int x=gi(),fx=t1.fset(x);
qa[fx].push_back(make_pair(t1.sze[fx],i));
}
if(c=='Z')
{
int x=gi(),fx=t2.fset(x);
qz[fx].push_back(i);
}
if(c=='Q')
{
int x=gi();
qq[x].push_back(i);
}
}
for(int i=1;i<=t2.id;++i) if(t2.f[i]==i) dfs2(i);
for(int i=1;i<=t1.id;++i) if(t1.f[i]==i) dfs1(i);
for(int i=1;i<=m;++i) if(op[i][0]=='Q') printf("%lld\n",ans[i]);
}
ARC093F Dark Horse
固定 \(1\) 的位置,题意可以转化为长度分别为 \(2^0,2^1,\dots 2^{n-1}\) 的区间的最小值不能为给定的 \(m\) 个数之一。
考虑容斥,枚举违反限制的区间的集合 \(S\),强制让这些区间的最小值为给定数。考虑 DP 每个集合的方案数。设 \(f_{i,S}\) 表示考虑从大到小前 \(i\) 个给定数,已被钦定的区间集合为 \(S\) ,填这些区间的方案数。新钦定一个区间可以将 \(a_i\) 和 \(\gt a_i\) 的未填的数填入当前区间。最后没被钦定的位置随意填数即可。
#include<bits/stdc++.h>
using namespace std;
#define pcnt __builtin_popcount
const int N=(1<<16)+5,M=18,Mod=1e9+7;
int n,m,an,a[M],fac[N],inv[N],f[M][N],ans;
#define mul(x,y) (1ll*(x)*(y)%Mod)
inline void upd(int& x, int y)
{ x=(x+y>=Mod?x+y-Mod:x+y);
}
inline int po(int x, int y=Mod-2)
{
int r=1;
for(;y;y>>=1,x=mul(x,x)) if(y&1) r=mul(r,x);
return r;
}
inline int C(int x, int y)
{
if(x<y||x<0||y<0) return 0;
return mul(fac[x],mul(inv[y],inv[x-y]));
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=m;++i) scanf("%d",&a[i]);
sort(a+1,a+1+m,greater<int>());
an=(1<<n),fac[0]=inv[0]=f[0][0]=1;
for(int i=1;i<=an;++i) fac[i]=mul(i,fac[i-1]);
inv[an]=po(fac[an],Mod-2);
for(int i=an-1;i;--i) inv[i]=mul(i+1,inv[i+1]);
for(int i=1;i<=m;++i)
for(int s=0;s<an;++s) if(f[i-1][s])
{
upd(f[i][s],f[i-1][s]);
for(int j=0;j<n;++j) if(~s&(1<<j))
upd(f[i][s|(1<<j)],mul(f[i-1][s],mul(C(an-a[i]-s,(1<<j)-1),fac[1<<j])));
}
for(int s=0;s<an;++s)
{
int t=mul(f[m][s],fac[an-1-s]);
ans=(pcnt(s)&1)?(ans-t+Mod)%Mod:(ans+t)%Mod;
}
printf("%d\n",mul(ans,an));
}
CF587F Duff is Mad
建 AC 自动机,根号分治:
- 对于串长大于根号的串,把前缀插到 fail 树里,然后扫每个串求子树和;
- 对于串长小于根号的串就倒过来,扫每个串把子树 +1,然后询问求所有前缀的和。
扫描线树状数组维护即可。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
inline int gi()
{
char c=getchar(); int x=0;
for(;c<'0'||c>'9';c=getchar());
for(;c>='0'&&c<='9';c=getchar())x=(x<<1)+(x<<3)+c-'0';
return x;
}
const int N=1e5+5;
int n,q,b,ch[N][27],pos[N],fa[N],len[N],cnt=1,fail[N],dfn[N],ed[N],tid;
char s[N]; ll t[N],ans[N];
struct node
{
int u,id,w;
}; vector<node> qs[N],qb[N];
vector<int> e[N];
bool operator < (node x, node y)
{ return x.u<y.u;
}
void upd(int i, int w)
{
for(;i&&i<=cnt;i+=(i&-i)) t[i]+=w;
}
ll qry(int i)
{
int r=0;
for(;i;i-=(i&-i)) r+=t[i];
return r;
}
void upd(int l, int r, int w)
{ upd(l,w),upd(r+1,-w);
}
ll qry(int l, int r)
{ return qry(r)-qry(l-1);
}
void dfs(int u)
{
dfn[u]=++tid;
for(auto v:e[u]) dfs(v);
ed[u]=tid;
}
int main()
{
freopen("sol.in","r",stdin);
scanf("%d%d",&n,&q);
for(int i=1;i<=n;++i)
{
scanf("%s",s),b+=(len[i]=strlen(s));
int u=1;
for(int j=0;s[j];++j)
{
if(!ch[u][s[j]-'a']) ch[u][s[j]-'a']=++cnt,fa[cnt]=u;
u=ch[u][s[j]-'a'];
}
pos[i]=u;
}
queue<int> Q; Q.push(1);
while(!Q.empty())
{
int u=Q.front(); Q.pop();
for(int i=0;i<26;++i)
if(!ch[u][i]) ch[u][i]=(fail[u]?ch[fail[u]][i]:1);
else fail[ch[u][i]]=(fail[u]?ch[fail[u]][i]:1),Q.push(ch[u][i]);
}
for(int i=2;i<=cnt;++i) e[fail[i]].push_back(i);
dfs(1); b=sqrt(b);
for(int i=1;i<=q;++i)
{
int l=gi(),r=gi(),k=gi();
if(len[k]>b) qb[k].push_back((node){l-1,i,-1}),qb[k].push_back((node){r,i,1});
else qs[l-1].push_back((node){k,i,-1}),qs[r].push_back((node){k,i,1});
}
for(int i=1,k;i<=n;++i) if(len[i]>b)
{
for(int u=pos[i];u!=1;u=fa[u]) upd(dfn[u],1);
sort(qb[i].begin(),qb[i].end());
ll now=0;
for(k=0;k<qb[i].size()&&!qb[i][k].u;++k);
for(int j=1;j<=n;++j)
{
now+=qry(dfn[pos[j]],ed[pos[j]]);
for(;k<qb[i].size()&&qb[i][k].u==j;++k)
ans[qb[i][k].id]+=qb[i][k].w*now;
}
for(int u=pos[i];u!=1;u=fa[u]) upd(dfn[u],-1);
}
for(int i=1;i<=n;++i)
{
upd(dfn[pos[i]],ed[pos[i]],1);
for(auto x:qs[i])
for(int u=pos[x.u];u!=1;u=fa[u]) ans[x.id]+=x.w*qry(dfn[u]);
}
for(int i=1;i<=q;++i) printf("%lld\n",ans[i]);
}
ARC096E Everything on It
计数看到限制先考虑容斥:钦定 \(i\) 个数的出现次数不超过一次。设 \(f_{i,j}\) 表示 \(i\) 个数分到 \(j\) 个集合,允许数不放的方案数。类似斯特林数 DP:
(实际这个方案数相当于 \(i+1\brace {j+1}\) )。
其他 \(n-i\) 个数组成的集合可以随便选(\(2^{2^{n-i}}\)),并且也可以放入违反限制的 \(j\) 个集合中 ( \((2^{n-i})^j\) )。
故答案为:
#include<bits/stdc++.h>
using namespace std;
const int N=3005;
int c[N][N],f[N][N],n,Mod,ans;
#define mul(x,y) (1ll*(x)*(y)%Mod)
inline int add(int x, int y)
{ return (x+y>=Mod?x+y-Mod:x+y);
}
inline int po(int x, int y, int M=Mod)
{
int r=1;
for(;y;y>>=1,x=1ll*x*x%M) if(y&1) r=1ll*r*x%M;
return r;
}
int main()
{
scanf("%d%d",&n,&Mod);
for(int i=0;i<=n;++i) f[i][0]=c[i][0]=f[i][i]=c[i][i]=1;
for(int i=1;i<=n;++i)
for(int j=1;j<=i;++j)
{
c[i][j]=add(c[i-1][j],c[i-1][j-1]);
f[i][j]=add(f[i-1][j-1],mul(f[i-1][j],j+1));
}
for(int i=0;i<=n;++i)
{
int tmp=mul(c[n][i],po(2,po(2,n-i,Mod-1))),res=0,bs=po(2,n-i);
if(i&1) tmp=Mod-tmp;
for(int j=0,k=1;j<=i;++j,k=mul(k,bs))
res=add(res,mul(f[i][j],k));
ans=add(ans,mul(tmp,res));
}
printf("%d\n",ans);
}
ARC101E Ribbons on Tree
菜比选手又裸搬题解辣
考虑容斥,定义 \(f(S)\) 表示有足 \(S\) 中的边都不被覆盖的匹配方案,那么答案是 \(\sum_{S}(-1)^{|S|}f(S)\)。
考虑 DP 计算,设 \(f_{u,i}\) 表示 \(u\) 为根的子树,有 \(i\) 个点没被匹配的方案数。则对于 \(i=0\) 有 \(f_{u,0}=(-1\times[u\neq \text{root}])\sum_j f_{u,j}\times g_j\) ,其中 \(g_j\) 表示 \(j\) 个点两两匹配的方案数,同 AGC028D。前面的系数表示 \(u\) 不为根时 \(u\) 到父亲的边未被覆盖,所以要乘上容斥系数 \(-1\) 。
DP 直接合并子树即可,复杂度 \(O(N^2)\)。
#include<bits/stdc++.h>
using namespace std;
const int N=5005,Mod=1e9+7;
vector<int> e[N];
int f[N][N],g[N],t[N],sze[N],n;
#define mul(x,y) (1ll*(x)*(y)%Mod)
inline int add(int x, int y)
{ return (x+y>=Mod?x+y-Mod:x+y);
}
inline int sub(int x, int y)
{ return (x-y<0?x-y+Mod:x-y);
}
void dfs(int u, int fa)
{
sze[u]=1,f[u][1]=1;
for(auto v:e[u]) if(v!=fa)
{
dfs(v,u);
for(int i=1;i<=sze[u]+sze[v];++i) t[i]=0;
for(int i=1;i<=sze[u];++i)
for(int j=0;j<=sze[v];++j)
t[i+j]=add(t[i+j],mul(f[u][i],f[v][j]));
sze[u]+=sze[v];
for(int i=1;i<=sze[u];++i) f[u][i]=t[i];
}
for(int i=1;i<=sze[u];++i) f[u][0]=sub(f[u][0],mul(f[u][i],g[i]));
}
int main()
{
scanf("%d",&n);
g[0]=1;
for(int i=2;i<=n;++i) g[i]=mul(i-1,g[i-2]);
for(int i=1,u,v;i<n;++i)
{
scanf("%d%d",&u,&v);
e[u].push_back(v),e[v].push_back(u);
}
dfs(1,0),printf("%d\n",sub(0,f[1][0]));
}
CF585F Digits of Number Pi
将 \(s\) 的所有长度为 \(\frac{d}{2}\) 的子串加到 AC 自动机,然后在自动机上数位 DP:设 \(f_{i,u,0/1}\) 表示匹配了前 \(i\) 位,在自动机上节点是 \(u\) ,是否贴上界。在每个终止位置统计答案即可。
#include<bits/stdc++.h>
using namespace std;
const int N=1005,M=55,L=N*M,Mod=1e9+7;
char s[N],x[M],y[M],po[M],g[M];
int n,d,ch[L][10],fail[L],f[M][L][2],cnt=1;
bool ed[L];
#define mul(x,y) (1ll*(x)*(y)%Mod)
inline void upd(int& x, int y)
{ x=(x+y>=Mod?x+y-Mod:x+y);
}
inline int sub(int x, int y)
{ return (x-y<0?x-y+Mod:x-y);
}
void insert(int l, int r)
{
int u=1;
for(int i=l;i<=r;++i)
{
if(!ch[u][s[i]]) ch[u][s[i]]=++cnt;
u=ch[u][s[i]];
}
ed[u]=true;
}
inline int solve(char* s)
{
for(int i=1;i<=d;++i) s[i]-='0';
memset(f,0,sizeof(f));
f[0][1][1]=1;
for(int i=1;i<=d;++i)
for(int u=1;u<=cnt;++u) if(!ed[u])
for(int j=0;j<10;++j) if(ch[u][j])
{
const int v=ch[u][j];
upd(f[i][v][0],f[i-1][u][0]);
if(s[i]>j) upd(f[i][v][0],f[i-1][u][1]);
if(s[i]==j) upd(f[i][v][1],f[i-1][u][1]);
}
int ans=0;
for(int i=d,x=1,y=1;i;--i)
{
for(int u=1;u<=cnt;++u) if(ed[u])
upd(ans,mul(f[i][u][0],x)),upd(ans,mul(f[i][u][1],y));
upd(y,mul(x,s[i])),x=mul(x,10);
}
return ans;
}
int main()
{
scanf("%s%s%s",s+1,x+1,y+1);
n=strlen(s+1),d=strlen(x+1);
for(int i=d;i;--i)
{
if(x[i]=='0') x[i]='9';
else
{
--x[i];
break;
}
}
for(int i=1;i<=n;++i) s[i]-='0';
for(int i=1;i<=n-d/2+1;++i) insert(i,i+d/2-1);
queue<int> q; q.push(1);
while(!q.empty())
{
int u=q.front(); q.pop();
for(int i=0;i<10;++i)
{
if(!ch[u][i]) ch[u][i]=(fail[u]?ch[fail[u]][i]:1);
else fail[ch[u][i]]=(fail[u]?ch[fail[u]][i]:1),q.push(ch[u][i]);
}
}
printf("%d\n",sub(solve(y),solve(x)));
}
CF587D Duff in Mafia
二分答案,转化为判断性问题。题目的限制可以考虑用 2-SAT 来描述。\(x_i\) 表示选第 \(i\) 条边。
对于一个点连接的边,如果有一对边颜色相同,显然只能选其中一条;超过一对显然无解。
对于一个点连接的边只能选一条,相当于有 \(deg^2\) 条限制:\(x_i\to \neg x_j\) 。考虑前缀优化:\(s_i\) 表示在前 \(i\) 个点中匹配,则有 \(s_{i-1}\to s_i,~s_{i-1}\to \neg x_i,~~x_i\to s_i\) 。
#include<bits/stdc++.h>
using namespace std;
inline int gi()
{
char c=getchar(); int x=0;
for(;c<'0'||c>'9';c=getchar());
for(;c>='0'&&c<='9';c=getchar())x=(x<<1)+(x<<3)+c-'0';
return x;
}
const int N=3e5+5;
struct node
{
int v,col,w,id;
}; vector<node> e[N];
bool operator < (node x, node y)
{
return x.col<y.col;
}
int n,m,id,cnt,L,R,bel[N],scc,dfn[N],low[N],tid,stk[N],tp;
bool ins[N]; vector<int> ne[N],ans;
void adde(int u, int v, int c, int w, int id)
{
e[u].push_back((node){v,c,w,id});
e[v].push_back((node){u,c,w,id});
}
void adde(int u, int v)
{
ne[u].push_back(v);
ne[v^1].push_back(u^1);
}
void tarjan(int u)
{
dfn[u]=low[u]=++tid;
stk[++tp]=u,ins[u]=true;
for(auto v:ne[u])
{
if(!dfn[v]) tarjan(v),low[u]=min(low[u],low[v]);
else if(ins[v]) low[u]=min(low[u],dfn[v]);
}
if(dfn[u]==low[u])
{
++scc; int v;
do
{
v=stk[tp--];
ins[v]=false,bel[v]=scc;
} while(u!=v);
}
}
void init()
{
#define clr(x) memset(x,0,sizeof(x))
clr(bel),clr(dfn),clr(low),scc=tp=tid=0;
for(int i=1;i<=2*cnt+1;++i) ne[i].clear();
cnt=m;
}
bool check(int mid)
{
init();
for(int u=1;u<=n;++u)
{
bool f=true;
for(int i=0;i<e[u].size();++i)
{
const int id=e[u][i].id;
adde(2*id,2*(++cnt));
if(e[u][i].w>mid) adde(2*id,2*id+1);
if(!i) continue;
const int lid=e[u][i-1].id;
adde(2*(cnt-1),2*cnt);
adde(2*(cnt-1),2*id+1);
if(e[u][i-1].col==e[u][i].col)
{
if(f) f=false;
else return false;
adde(2*lid+1,2*id);
adde(2*lid,2*id+1);
}
}
}
for(int i=1;i<=2*cnt+1;++i) if(!dfn[i]) tarjan(i);
for(int i=1;i<=cnt;++i) if(bel[2*i]==bel[2*i+1]) return false;
return true;
}
int main()
{
n=gi(),m=gi();
for(int i=1;i<=m;++i)
{
int u=gi(),v=gi(),c=gi(),w=gi();
adde(u,v,c,w,i),R=max(R,w);
}
for(int i=1;i<=n;++i) sort(e[i].begin(),e[i].end());
while(L<=R)
{
int mid=L+R>>1;
check(mid)?R=mid-1:L=mid+1;
}
if(!check(R+1))
{
puts("No");
return 0;
}
for(int i=1;i<=m;++i) if(bel[2*i]<bel[2*i+1]) ans.push_back(i);
printf("Yes\n%d %d\n",R+1,ans.size());
for(auto x:ans) printf("%d ",x);
}
CF512D Fox And Travelling
显然环上的点和被环上的点“隔离”的点无法被访问,故可用拓扑排序求出能被访问的点,则剩下的连通块均为树。
考虑与无法访问的点相连的点,则该点在其所在的连通块中一定最后访问。显然一个连通块只有一个这样的点,所以可以以该点为根做树形 DP:设 \(f_{u,i}\) 表示以 \(u\) 为根的子树,访问 \(i\) 个点,\(u\) 最后访问的方案数。直接把子树有序合并即可。
如果一个连通块里没有这样的点,直接枚举每个点为根算方案数求和,这样对于有 \(i\) 个点没选的方案,它会在以这 \(i\) 个点中的每一个为根时都被计算一次,因此将 \(i\) 个点没选的方案数除以 \(i\) 即可去重。复杂度 \(O(n^3)\)。
#include<bits/stdc++.h>
using namespace std;
const int N=105,Mod=1e9+9;
vector<int> e[N],r1,r2,s[N],ne[N];
int n,m,fac[N],inv[N],deg[N],f[N][N],g[N],h[N],t[N],nsz,sze[N];
bool able[N],vis[N];
#define mul(x,y) (1ll*(x)*(y)%Mod)
inline void upd(int& x, int y)
{ x=(x+y>=Mod?x+y-Mod:x+y);
}
inline int po(int x, int y=Mod-2)
{
int r=1;
for(;y;y>>=1,x=mul(x,x)) if(y&1) r=mul(r,x);
return r;
}
inline int C(int x, int y)
{
if(x<0||y<0||x<y) return 0;
return mul(fac[x],mul(inv[y],inv[x-y]));
}
void dfs1(int u, int fa)
{
vis[u]=true;
for(auto v:e[u])
if(able[v]&&!vis[v])
ne[u].push_back(v),ne[v].push_back(u),dfs1(v,u);
}
void dfs2(int u, int b, int fa)
{
vis[u]=true,s[b].push_back(u);
for(auto v:e[u])
if(able[v]&&!vis[v])
ne[u].push_back(v),ne[v].push_back(u),dfs2(v,b,u);
}
void comb(int* f, int* g, int& s1, int s2)
{
for(int i=0;i<=s1;++i)
for(int j=0;j<=s2;++j)
upd(t[i+j],mul(mul(f[i],g[j]),C(i+j,j)));
s1+=s2;
for(int i=0;i<=s1;++i) f[i]=t[i],t[i]=0;
}
void dfs(int u, int fa)
{
f[u][sze[u]=0]=1;
for(auto v:ne[u]) if(v!=fa) dfs(v,u),comb(f[u],f[v],sze[u],sze[v]);
++sze[u],f[u][sze[u]]=f[u][sze[u]-1];
}
int main()
{
scanf("%d%d",&n,&m);
fac[0]=inv[0]=1;
for(int i=1;i<=n;++i) fac[i]=mul(i,fac[i-1]);
inv[n]=po(fac[n]);
for(int i=n-1;i;--i) inv[i]=mul(i+1,inv[i+1]);
for(int i=1,u,v;i<=m;++i)
{
scanf("%d%d",&u,&v);
++deg[u],++deg[v];
e[u].push_back(v),e[v].push_back(u);
}
queue<int> q;
for(int i=1;i<=n;++i) if(deg[i]<=1) q.push(i);
while(!q.empty())
{
int u=q.front(); q.pop();
able[u]=true;
for(auto v:e[u]) if((--deg[v])==1) q.push(v);
}
for(int i=1;i<=n;++i) if(deg[i]==1) dfs1(i,0),r1.push_back(i);
for(int i=1;i<=n;++i) if(able[i]&&!vis[i]) dfs2(i,i,0),r2.push_back(i);
g[0]=1;
for(auto u:r1) dfs(u,0),comb(g,f[u],nsz,sze[u]);
for(auto x:r2)
{
for(auto u:s[x])
{
dfs(u,0);
for(int i=0;i<=sze[u];++i) upd(h[i],f[u][i]);
}
const int sz=sze[s[x].back()];
for(int i=0;i<sz;++i) h[i]=mul(h[i],po(sz-i));
comb(g,h,nsz,sz);
for(int i=0;i<=sz;++i) h[i]=0;
}
for(int i=0;i<=n;++i) printf("%d\n",g[i]);
}
CF521D shop
显然三种操作先赋值,然后加,最后乘。
赋值一定选择最大的赋值,由于赋值一定最先操作,所以可以看成加法操作。
对于加法,肯定选连续的前若干大的操作。由于加法一定在乘法后操作,所以可以将从大到小第 \(i\) 个操作看作乘上 \(\dfrac{s+b_i}{s}\) (\(s=a_i+\sum_{j=1}^{i-1}b_i\) )。
这样所有操作就转化为了乘法操作,选前 \(k\) 大后按操作类型排序即可。
#include<bits/stdc++.h>
using namespace std;
typedef pair<int,int> pri;
typedef pair<double,int> prd;
#define fi first
#define se second
const int N=1e5+5;
inline int gi()
{
char c=getchar(); int x=0;
for(;c<'0'||c>'9';c=getchar());
for(;c>='0'&&c<='9';c=getchar())x=(x<<1)+(x<<3)+c-'0';
return x;
}
int n,m,k,o[N];
long long a[N];
pri app[N];
vector<pri> add[N];
vector<prd> mul;
int main()
{
n=gi(),m=gi(),k=gi();
for(int i=1;i<=n;++i) a[i]=gi();
for(int i=1;i<=m;++i)
{
int op=gi(),x=gi(),w=gi();
if(op==1) app[x]=max(app[x],pri(w,i));
if(op==2) add[x].push_back(pri(w,i));
if(op==3) mul.push_back(prd(w,i));
o[i]=op;
}
for(int i=1;i<=n;++i)
if(app[i].fi>a[i]) add[i].push_back(pri(app[i].fi-a[i],app[i].se));
for(int i=1;i<=n;++i)
{
sort(add[i].begin(),add[i].end(),greater<pri>());
for(auto x:add[i])
{
mul.push_back(prd(1.0*(a[i]+x.fi)/a[i],x.se));
a[i]+=x.fi;
}
}
sort(mul.begin(),mul.end(),greater<prd>());
const int l=min(k,(int)mul.size());
printf("%d\n",l);
sort(mul.begin(),mul.begin()+l,[&](prd x, prd y){
return o[x.se]<o[y.se];
});
for(int i=0;i<l;++i) printf("%d ",mul[i].se);
}
CF582E Boolean Function
建出表达式树后 DP:我们设 \(S\) 为 \(f(A,B,C,D)=1\) 的 \((A,B,C,D)\) 的集合(注意这里 \(S\) 是所有 \((A,B,C,D)\) 取值集合的集合),\(dp_{u,S}\) 表示表达式树上以 \(u\) 为根,集合为 \(S\) 的方案数。可以转移为左右子树做 and
/ or
卷积,fwt 即可。
#include<bits/stdc++.h>
using namespace std;
const int m=(1<<16),Mod=1e9+7,N=m+5,M=505;
char s[M]; vector<int> vt;
int len,f[M][N],tA[4],ta[4],t[N],ch[N][2],tl[N],tr[N],tt[N],rt,cnt,n,q[2],ans;
#define mul(x,y) (1ll*(x)*(y)%Mod)
inline void add(int& x, int y)
{ x=(x+y>=Mod?x+y-Mod:x+y);
}
inline void sub(int& x, int y)
{ x=(x-y<0?x-y+Mod:x-y);
}
void fwt_or(int* a, int o)
{
for(int i=1;i<m;i<<=1)
for(int j=0;j<m;j+=(i<<1))
for(int k=0;k<i;++k)
o==1?add(a[i+j+k],a[j+k]):sub(a[i+j+k],a[j+k]);
}
void fwt_and(int* a, int o)
{
for(int i=1;i<m;i<<=1)
for(int j=0;j<m;j+=(i<<1))
for(int k=0;k<i;++k)
o==1?add(a[j+k],a[i+j+k]):sub(a[j+k],a[i+j+k]);
}
int solve(int l, int r)
{
if(l==r)
{
int ns=0;
if('A'<=s[l]&&s[l]<='D') f[l][tA[s[l]-'A']]=1;
if('a'<=s[l]&&s[l]<='d') f[l][ta[s[l]-'a']]=1;
if(s[l]=='?') for(auto s:vt) ++f[l][s];
return l;
}
int t=1,x;
for(x=l+1;t;++x) t+=(s[x]=='('?1:(s[x]==')'?-1:0));
ch[x][0]=solve(l+1,x-2),ch[x][1]=solve(x+2,r-1);
if(s[x]!='|')
{
memcpy(tl,f[ch[x][0]],sizeof(tl));
memcpy(tr,f[ch[x][1]],sizeof(tr));
fwt_and(tl,1),fwt_and(tr,1);
for(int i=0;i<m;++i) tt[i]=mul(tl[i],tr[i]);
fwt_and(tt,-1);
for(int i=0;i<m;++i) add(f[x][i],tt[i]);
}
if(s[x]!='&')
{
memcpy(tl,f[ch[x][0]],sizeof(tl));
memcpy(tr,f[ch[x][1]],sizeof(tr));
fwt_or(tl,1),fwt_or(tr,1);
for(int i=0;i<m;++i) tt[i]=mul(tl[i],tr[i]);
fwt_or(tt,-1);
for(int i=0;i<m;++i) add(f[x][i],tt[i]);
}
return x;
}
int main()
{
scanf("%s",s+1),len=strlen(s+1);
for(int i=0;i<4;++i)
{
int ns=0;
for(int s=0;s<(1<<4);++s)
if(s&(1<<i)) ns|=(1<<s);
tA[i]=ns,ta[i]=ns^(m-1),vt.push_back(ns),vt.push_back(ns^(m-1));
}
rt=solve(1,len);
scanf("%d",&n);
while(n--)
{
int a,b,c,d,e;
scanf("%d%d%d%d%d",&a,&b,&c,&d,&e);
q[e]|=1<<(a|(b<<1)|(c<<2)|(d<<3));
}
for(int s=0;s<m;++s)
if(!(s&q[0])&&(s&q[1])==q[1]) add(ans,f[rt][s]);
printf("%d\n",ans);
}
CF559E Gerald and Path
将坐标离散化,将线段按 \(a_i\) 升序排序。设 \(f_{i,x}\) 表示前 \(i\) 个线段,覆盖的最远距离为 \(x\) 的总覆盖长度。
对于线段向右,直接通过 \(x\) 即可得到新覆盖的长度;
对于线段向左,我们考虑枚举最近的一条向左的线段,计算当前线段和中间所有向右的线段的贡献和。预处理区间右端点最大值后可计算新覆盖的长度。
暴力转移代码如下:
chmax(f[i][max(a[i].r,k)],f[j][k]+max(0,min(v[a[i].r]-v[k],v[a[i].r]-v[a[i].m])));
chmax(f[i][max(a[i].m,mx[j+1][i-1])],f[j][k]+max(v[a[i].m],v[mx[j+1][i-1]])-max(v[a[i].l],v[k]));
时间复杂度是 \(O(n^3)\) 的。
发现对于第一个式子可以记第二维的前缀最大值,对于第二个式子可以将第二个 max 拆开后记前后缀最大值,即可做到 \(O(n)\) 转移。时间复杂度 \(O(n^2)\)。
暴力转移(\(O(n^4)\))完整代码:
#include<bits/stdc++.h>
using namespace std;
const int N=105,M=505;
struct node
{
int l,m,r;
bool operator < (const node x) const
{
return m<x.m;
}
} a[N];
int f[N][M],n,v[M],mx[N][N],t,ans;
#define Get(x) lower_bound(v+1,v+1+t,x)-v
inline int chmax(int& x, int y)
{ x=(x>y?x:y);
}
int main()
{
scanf("%d",&n);
v[++t]=-1e9;
for(int i=1,x;i<=n;++i)
{
scanf("%d%d",&a[i].m,&x);
a[i].l=a[i].m-x,a[i].r=a[i].m+x;
v[++t]=a[i].l,v[++t]=a[i].m,v[++t]=a[i].r;
}
sort(v+1,v+1+t),t=unique(v+1,v+1+t)-v-1;
for(int i=1;i<=n;++i) a[i].l=Get(a[i].l),a[i].m=Get(a[i].m),a[i].r=Get(a[i].r);
sort(a+1,a+1+n);
for(int i=1;i<=n;++i)
{
mx[i][i]=a[i].r;
for(int j=i+1;j<=n;++j) mx[i][j]=max(mx[i][j-1],a[j].r);
}
memset(f,-0x3f,sizeof(f));
f[0][1]=0;
for(int i=1;i<=n;++i)
for(int j=0;j<i;++j)
for(int k=1;k<=t;++k) if(f[j][k]!=f[0][0])
{
chmax(f[i][max(a[i].r,k)],f[j][k]+max(0,min(v[a[i].r]-v[k],v[a[i].r]-v[a[i].m])));
chmax(f[i][max(a[i].m,mx[j+1][i-1])],f[j][k]+max(v[a[i].m],v[mx[j+1][i-1]])-max(v[a[i].l],v[k]));
}
for(int i=1;i<=n;++i)
for(int j=1;j<=t;++j) chmax(ans,f[i][j]);
printf("%d\n",ans);
}
\(O(n^3)\) 代码:
#include<bits/stdc++.h>
using namespace std;
const int N=105,M=505;
struct node
{
int l,m,r;
bool operator < (const node x) const
{
return m<x.m;
}
} a[N];
int f[N][M],g[M],h1[N][M],h2[N][M],n,v[M],mx[N][N],t,ans;
#define Set(x) memset(x,-0x3f,sizeof(x))
#define Get(x) lower_bound(v+1,v+1+t,x)-v
inline int chmax(int& x, int y)
{ x=(x>y?x:y);
}
void upd(int i)
{
for(int k=1;k<=t;++k)
g[k]=max(g[k],f[i][k]),h1[i][k]=max(h1[i][k-1],f[i][k]);
for(int k=t;k;--k) h2[i][k]=max(h2[i][k+1],f[i][k]-v[k]);
}
int main()
{
scanf("%d",&n);
v[++t]=-1e9;
for(int i=1,x;i<=n;++i)
{
scanf("%d%d",&a[i].m,&x);
a[i].l=a[i].m-x,a[i].r=a[i].m+x;
v[++t]=a[i].l,v[++t]=a[i].m,v[++t]=a[i].r;
}
sort(v+1,v+1+t),t=unique(v+1,v+1+t)-v-1;
for(int i=1;i<=n;++i) a[i].l=Get(a[i].l),a[i].m=Get(a[i].m),a[i].r=Get(a[i].r);
sort(a+1,a+1+n);
for(int i=1;i<=n;++i)
{
mx[i][i]=a[i].r;
for(int j=i+1;j<=n;++j) mx[i][j]=max(mx[i][j-1],a[j].r);
}
Set(f),Set(g),Set(h1),Set(h2);
f[0][1]=0,upd(0);
for(int i=1;i<=n;++i)
{
for(int k=1;k<=t;++k)
chmax(f[i][max(a[i].r,k)],g[k]+max(0,min(v[a[i].r]-v[k],v[a[i].r]-v[a[i].m])));
for(int j=0;j<i;++j)
chmax(f[i][max(a[i].m,mx[j+1][i-1])],max(h1[j][a[i].l]-v[a[i].l],h2[j][a[i].l+1])+max(v[a[i].m],v[mx[j+1][i-1]]));
upd(i);
}
for(int i=1;i<=n;++i)
for(int j=1;j<=t;++j) chmax(ans,f[i][j]);
printf("%d\n",ans);
}