[CCO2019] Sirtet
首先一个连通块内的点下落的距离相同,令 \(h_{i,j}\) 表示 \((i,j)\) 所在连通块下降的距离,对于一个 \((a,j)\),其中 \(a>i\),\(h_{a,j}\le h_{i,j}\le h_{a,j}+i-a-1\),后面一个不等号取等当且仅当 \((i,j)\) 最后落在 \((a'+1,j)\)。
可以发现这是一个差分约束的形式,跑一遍差分约束即可。
点击查看代码
#include<bits/stdc++.h>
#define ull unsigned long long
#define ll long long
#define pii pair<int,int>
#define pdi pair<double,int>
#define pb push_back
#define eps 1e-9
#define mp make_pair
using namespace std;
const int inf=1e9;
namespace IO{
template<typename T>
inline void read(T &x){
x=0;
int f=1;
char ch=getchar();
while(ch>'9'||ch<'0'){
if(ch=='-'){
f=-1;
}
ch=getchar();
}
while(ch>='0'&&ch<='9'){
x=x*10+(ch-'0');
ch=getchar();
}
x=(f==1?x:-x);
}
template<typename T>
inline void write(T x){
if(x<0){
putchar('-');
x=-x;
}
if(x>=10){
write(x/10);
}
putchar(x%10+'0');
}
template<typename T>
inline void write_endl(T x){
write(x);
putchar('\n');
}
template<typename T>
inline void write_space(T x){
write(x);
putchar(' ');
}
}
using namespace IO;
const int N=1e6+10;
int n,m,fa[N],head[N],tot;
vector<bool>a[N],res[N];
int id(int x,int y){
return (x-1)*m+y;
}
int getfa(int x){
if(x==fa[x]){
return x;
}
fa[x]=getfa(fa[x]);
return fa[x];
}
void merge(int u,int v){
u=getfa(u),v=getfa(v);
if(u!=v){
fa[v]=u;
}
}
struct edge{
int v,w,nxt;
}e[N<<2];
void add(int u,int v,int w){
u=getfa(u),v=getfa(v);
e[++tot].v=v;
e[tot].w=w;
e[tot].nxt=head[u];
head[u]=tot;
// cerr<<head[u]<<endl;
}
int dis[N],vis[N];
void dij(){
priority_queue<pii>q;
for(int i=1;i<=n*m;i++){
dis[i]=inf;
}
q.push(mp(0,0));
while(q.size()){
int u=q.top().second;
// cerr<<u<<endl;
q.pop();
if(vis[u]){
continue;
}
vis[u]=1;
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].v,w=e[i].w;
if(dis[v]>dis[u]+w){
dis[v]=dis[u]+w;
q.push(mp(-dis[v],v));
}
}
}
}
void solve(){
read(n),read(m);
a[0].resize(m+10);
a[n+1].resize(m+10);
for(int i=1;i<=n;i++){
a[i].resize(m+10);
res[i].resize(m+10);
for(int j=1;j<=m;j++){
char opt=getchar();
while(opt!='.'&&opt!='#'){
opt=getchar();
}
a[i][j]=(opt=='#');
}
}
for(int i=1;i<=n*m;i++){
fa[i]=i;
}
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
if(!a[i][j]){
continue;
}
if(a[i-1][j]){
merge(id(i,j),id(i-1,j));
}
if(a[i][j-1]){
merge(id(i,j),id(i,j-1));
}
if(a[i+1][j]){
merge(id(i,j),id(i+1,j));
}
if(a[i][j+1]){
merge(id(i,j),id(i,j+1));
}
}
}
for(int j=1;j<=m;j++){
int lst=-1;
for(int i=1;i<=n;i++){
if(a[i][j]){
if(lst!=-1){
add(id(i,j),id(lst,j),i-lst-1);
}
lst=i;
}
if(lst!=-1){
add(0,id(lst,j),n-lst);
}
}
}
dij();
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
if(a[i][j]){
res[i+dis[getfa(id(i,j))]][j]=1;
}
}
}
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
putchar(res[i][j]?'#':'.');
}
putchar('\n');
}
}
signed main(){
#ifndef ONLINE_JUDGE
freopen("1.in","r",stdin);
freopen("1.out","w",stdout);
#endif
int t=1;
while(t--){
solve();
}
return 0;
}
MEX counting
第二个条件,其实是限制每个前缀 \(mex\) 都要处于一个区间内。令 \(f_{i,j,k}\) 表示 \([1,i]\) 的 \(mex\) 为 \(k\),恰有 \(j\) 个不同的大于 \(k\) 的数。
分类讨论转移:
- \(a_i\le k\),\(f_{i,j,k}\times k\rightarrow f_{i+1,j,k}\)。
- \(a_i\) 是已经出现过的大于 \(k\) 的数,\(f_{i,j,k}\times j\rightarrow f_{i+1,j,k}\)。
- \(a_i\) 是未出现过的大于 \(k\) 的数,\(f_{i,j,k}\rightarrow f_{i+1,j+1,k}\)。
- \(a_i=k\),设此时的 \(mex\) 为 \(x\),\(j>x-k-1\),\(A^j_{x-k-1}f_{i,j,k}\rightarrow f_{i+1,j-x+k+1,x}\)。
这里状态是 \(O(n^2k)\),转移是 \(O(n)\) 的,考虑优化这个转移。
将转移 \(4\) 的式子拆开,\(\frac{j!}{(j-x+k+1)}f_{i,j,k}\rightarrow f_{i+1,j-x+k+1,x}\),令 \(j'=j-x+k+1\),转移式变为了 \(\frac{j!}{j'!}f_{i,j,k}\rightarrow f_{i,j',x}\),转移系数变成了两个 \(j\) 的阶乘相除。
令 \(j=j+k,j'=j'+x\),\(A^{j-k}_{j+1-x}f_{i,j,k}\rightarrow f_{i,j',x}\),根据上面得出的,转移系数的分母应当是后面 dp 状态中的 \(j\),所以 \(j'=j+1\),可以前缀和转移。这样所有转移的复杂度均为 \(O(1)\) 了,总复杂度 \(O(n^2k)\)。
点击查看代码
#include<bits/stdc++.h>
#define ull unsigned long long
#define ll long long
#define pii pair<int,int>
#define pdi pair<double,int>
#define pb push_back
#define eps 1e-9
#define mp make_pair
using namespace std;
const int inf=1e9;
namespace IO{
template<typename T>
inline void read(T &x){
x=0;
int f=1;
char ch=getchar();
while(ch>'9'||ch<'0'){
if(ch=='-'){
f=-1;
}
ch=getchar();
}
while(ch>='0'&&ch<='9'){
x=x*10+(ch-'0');
ch=getchar();
}
x=(f==1?x:-x);
}
template<typename T>
inline void write(T x){
if(x<0){
putchar('-');
x=-x;
}
if(x>=10){
write(x/10);
}
putchar(x%10+'0');
}
template<typename T>
inline void write_endl(T x){
write(x);
putchar('\n');
}
template<typename T>
inline void write_space(T x){
write(x);
putchar(' ');
}
}
using namespace IO;
const int N=2e3+10,K=110,mod=998244353;
int n,k,mn[N],mx[N],f[2][N][N],s[2][N][N],fac[N],inv[N];
int qpow(int a,int b){
int res=1;
while(b){
if(b&1){
res=1ll*res*a%mod;
}
b>>=1;
a=1ll*a*a%mod;
}
return res;
}
void init(int MX){
fac[0]=1;
for(int i=1;i<=MX;i++){
fac[i]=1ll*fac[i-1]*i%mod;
}
inv[MX]=qpow(fac[MX],mod-2);
for(int i=MX-1;i>=0;i--){
inv[i]=1ll*inv[i+1]*(i+1)%mod;
}
}
void solve(){
init(2000);
// for(int i=1;i<=10;i++){
// cerr<<fac[i]<<' '<<inv[i]<<endl;
// }
read(n),read(k);
for(int i=1,x;i<=n;i++){
read(x);
mn[i]=max(0,x-k);
mx[i]=min(i,x+k);
}
f[0][0][0]=s[0][0][0]=1;
for(int i=1,opt=1;i<=n;i++,opt^=1){
for(int j=0;j<=i;j++){
for(int k=mn[i];k<=mx[i]&&k<=j;k++){
f[opt][j][k]=(f[opt][j][k]+1ll*f[opt^1][j][k]*j%mod)%mod;
if(j){
f[opt][j][k]=(f[opt][j][k]+f[opt^1][j-1][k])%mod;
}
if(j&&k){
f[opt][j][k]=(f[opt][j][k]+1ll*s[opt^1][j-1][min(k-1,mx[i-1])]*inv[j-k]%mod)%mod;
}
s[opt][j][k]=1ll*f[opt][j][k]*fac[j-k]%mod;
if(k){
s[opt][j][k]=(s[opt][j][k]+s[opt][j][k-1])%mod;
}
}
}
for(int j=0;j<i;j++){
for(int k=mn[i-1];k<=mx[i-1]&&k<=j;k++){
s[opt^1][j][k]=f[opt^1][j][k]=0;
}
}
}
int ans=0;
for(int i=0;i<=n;i++){
for(int j=mn[n];j<=mx[n]&&j<=i;j++){
ans=(ans+1ll*f[n&1][i][j]*fac[n-j]%mod*inv[n-i]%mod)%mod;
}
}
write_endl(ans);
}
signed main(){
#ifndef ONLINE_JUDGE
freopen("1.in","r",stdin);
freopen("1.out","w",stdout);
#endif
int t=1;
while(t--){
solve();
}
return 0;
}
[AGC015E] Mr.Aoki Incubator
令 \(S_A\) 表示集合 \(A\) 最终染色得到的节点,\(S_{A\cup B}=S_A\cup S_B\),证明容易,将染色看作一条边,从染色点连向被染色点,从 \(0\) 连向初始染色点,\(S_A,S_B\) 相当于一棵树,\(S_{A\cup B}\) 相当于两棵树的并,这启示我们将点可以分开考虑。
将所有点以 \(x\) 为关键字排序,\(mx_i\) 表示前缀速度最大值,\(mn_i\) 表示后缀速度最小值。令 \(f_i\) 表示染色 \([1,i]\),且初始染 \(i\) 的方案数。假定上一个染色的是 \(j\),覆盖不到的点就是位置在 \([j+1,i-1]\),速度在 \([mx_j,mn_i]\) 中的点,显然如果成功转移,就不应该存在这样的点。可以双指针维护转移区间与这样的点的数量,用前缀和转移即可。
点击查看代码
#include<bits/stdc++.h>
#define ull unsigned long long
#define ll long long
#define pii pair<int,int>
#define pdi pair<double,int>
#define pb push_back
#define eps 1e-9
#define mp make_pair
using namespace std;
const int inf=1e9;
namespace IO{
template<typename T>
inline void read(T &x){
x=0;
int f=1;
char ch=getchar();
while(ch>'9'||ch<'0'){
if(ch=='-'){
f=-1;
}
ch=getchar();
}
while(ch>='0'&&ch<='9'){
x=x*10+(ch-'0');
ch=getchar();
}
x=(f==1?x:-x);
}
template<typename T>
inline void write(T x){
if(x<0){
putchar('-');
x=-x;
}
if(x>=10){
write(x/10);
}
putchar(x%10+'0');
}
template<typename T>
inline void write_endl(T x){
write(x);
putchar('\n');
}
template<typename T>
inline void write_space(T x){
write(x);
putchar(' ');
}
}
using namespace IO;
const int N=2e5+10,mod=1e9+7;
int n,val[N],mx[N],mn[N],pos[N],f[N],s[N],sum,l,r=-1,L,R=-1;
struct point{
int x,y;
}p[N];
bool cmp(point x,point y){
return x.x<y.x;
}
bool check(int q_l,int q_r,int q_L,int q_R){
if(q_l>q_r||q_L>q_R){
return 0;
}
while(l<q_l){
sum-=(p[l].y>=L&&p[l].y<=R);
l++;
}
while(r<q_r){
r++;
sum+=(p[r].y>=L&&p[r].y<=R);
}
while(L<q_L){
sum-=(pos[L]>=l&&pos[L]<=r);
L++;
}
while(R<q_R){
R++;
sum+=(pos[R]>=l&&pos[R]<=r);
}
return sum;
}
void solve(){
read(n);
for(int i=1;i<=n;i++){
read(p[i].x),read(p[i].y);
val[i]=p[i].y;
}
// cerr<<"HAHA"<<endl;
sort(val+1,val+n+1);
for(int i=1;i<=n;i++){
p[i].y=lower_bound(val+1,val+n+1,p[i].y)-val;
}
sort(p+1,p+n+1,cmp);
for(int i=1;i<=n;i++){
mx[i]=max(mx[i-1],p[i].y);
}
mn[n+1]=n+1;
for(int i=n;i;i--){
mn[i]=min(mn[i+1],p[i].y);
}
for(int i=1;i<=n;i++){
pos[p[i].y]=i;
}
f[0]=s[0]=1;
int now=0;
for(int i=1;i<=n+1;i++){
while(check(now+1,i-1,mx[now],mn[i])){
now++;
}
f[i]=s[i-1];
if(now){
f[i]=(f[i]+mod-s[now-1])%mod;
}
s[i]=(s[i-1]+f[i])%mod;
}
write_endl(f[n+1]);
}
signed main(){
#ifndef ONLINE_JUDGE
freopen("1.in","r",stdin);
freopen("1.out","w",stdout);
#endif
int t=1;
while(t--){
solve();
}
return 0;
}
[HNOI2013] 切糕
将两个条件分开处理,第一个条件相当于对于每一列建一条长度为 \(R+1\) 的链,从 \((i,j,k)\) 向 \((i,j,k+1)\) 连一条流量为 \(v_{i,j,k}\) 的边,从 \(S\) 向 \((i,j,1)\) 连一条流量为 \(+\infty\) 的边,从 \((i,j,R+1)\) 向 \(T\) 连一条流量为 \(+\infty\) 的边,这样 \(S\) 到 \(T\) 的最小割就是最小权值和。
接下来处理出第二个条件,拆开式子 \(-D\le f(x,y)-f(x',y')\le D\)。因为反过来是一样的,所以只需要管 \(f(x,y)-f(x',y')\le D\)。这个很好处理,连一条 \((x,y,f(x,y))\) 向 \((x',y',f(x,y)-d)\) 连边,这样如果差值大于 \(d\),则需要多割边,更劣。答案为该图的最小割。
点击查看代码
#include<bits/stdc++.h>
#define ull unsigned long long
#define int long long
#define pii pair<int,int>
#define pdi pair<double,int>
#define pb push_back
#define eps 1e-9
#define mp make_pair
using namespace std;
const int inf=1e18;
namespace IO{
template<typename T>
inline void read(T &x){
x=0;
int f=1;
char ch=getchar();
while(ch>'9'||ch<'0'){
if(ch=='-'){
f=-1;
}
ch=getchar();
}
while(ch>='0'&&ch<='9'){
x=x*10+(ch-'0');
ch=getchar();
}
x=(f==1?x:-x);
}
template<typename T>
inline void write(T x){
if(x<0){
putchar('-');
x=-x;
}
if(x>=10){
write(x/10);
}
putchar(x%10+'0');
}
template<typename T>
inline void write_endl(T x){
write(x);
putchar('\n');
}
template<typename T>
inline void write_space(T x){
write(x);
putchar(' ');
}
}
using namespace IO;
const int N=4e5+10,Siz=50;
int tot=1,head[N],v[Siz][Siz][Siz],P,Q,R,d,S,T;
int dx[4]={1,0,-1,0},dy[4]={0,1,0,-1};
struct edge{
int v,w,nxt;
}e[N];
void add(int u,int v,int w){
e[++tot].v=v;
e[tot].w=w;
e[tot].nxt=head[u];
head[u]=tot;
}
void add_e(int u,int v,int w){
add(u,v,w);
add(v,u,0);
}
int id(int x,int y,int z){
return (z-1)*P*Q+(x-1)*Q+y;
}
namespace Max_Flow{
int cur[N<<1],dep[N<<1];
bool bfs(){
queue<int>q;
for(int i=1;i<=T;i++){
dep[i]=0;
}
dep[S]=1;
q.push(S);
while(!q.empty()){
int u=q.front();
// cerr<<u<<endl;
q.pop();
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].v,w=e[i].w;
if(!dep[v]&&w){
dep[v]=dep[u]+1;
if(v==T){
return 1;
}
q.push(v);
}
}
}
return 0;
}
int dfs(int u,int flow){
if(u==T){
return flow;
}
int ans=0;
for(int i=cur[u];i;i=e[i].nxt){
cur[u]=i;
int v=e[i].v,w=e[i].w;
if(dep[v]==dep[u]+1&&w){
int res=dfs(v,min(flow,w));
e[i].w-=res;
e[i^1].w+=res;
flow-=res;
ans+=res;
}
if(!flow){
break;
}
}
if(!ans){
dep[u]=0;
}
return ans;
}
int dinic(){
int ans=0;
while(bfs()){
// cerr<<"AHA"<<endl;
for(int i=1;i<=T;i++){
cur[i]=head[i];
}
ans+=dfs(S,inf);
}
return ans;
}
}
void solve(){
read(P),read(Q),read(R),read(d);
S=id(P,Q,R+1)+1,T=id(P,Q,R+1)+2;
for(int i=1;i<=R;i++){
for(int j=1;j<=P;j++){
for(int k=1;k<=Q;k++){
read(v[j][k][i]);
}
}
}
for(int i=1;i<=P;i++){
for(int j=1;j<=Q;j++){
add_e(S,id(i,j,1),inf);
add_e(id(i,j,R+1),T,inf);
for(int k=1;k<=R;k++){
add_e(id(i,j,k),id(i,j,k+1),v[i][j][k]);
}
}
}
for(int i=1;i<=P;i++){
for(int j=1;j<=Q;j++){
for(int way=0;way<4;way++){
int x=i+dx[way],y=j+dy[way];
if(x<1||y<1||x>P||y>Q){
continue;
}
for(int k=d+1;k<=R+1;k++){
add_e(id(i,j,k),id(x,y,k-d),inf);
}
}
}
}
write_endl(Max_Flow::dinic());
}
signed main(){
#ifndef ONLINE_JUDGE
freopen("1.in","r",stdin);
freopen("1.out","w",stdout);
#endif
int t=1;
while(t--){
solve();
}
return 0;
}
[COCI2015] Divljak
注意到询问是一个多模匹配问题,对于所有的串建出AC自动机。
对于一个新的串 \(P\),它能对集合中哪些 \(S\) 造成贡献。考虑匹配这个过程,相当于匹配到若干个点,将它一直往上跳 \(fail\) 的一段路径上点对应的串 \(+1\)。这是一个路径加,单点查询的过程。使用树上差分,可以变为单点加,子树查询,用树状数组维护即可。
点击查看代码
#include<bits/stdc++.h>
#define ull unsigned long long
#define ll long long
#define pii pair<int,int>
#define pdi pair<double,int>
#define pb push_back
#define eps 1e-9
#define mp make_pair
using namespace std;
const int inf=1e9;
namespace IO{
template<typename T>
inline void read(T &x){
x=0;
int f=1;
char ch=getchar();
while(ch>'9'||ch<'0'){
if(ch=='-'){
f=-1;
}
ch=getchar();
}
while(ch>='0'&&ch<='9'){
x=x*10+(ch-'0');
ch=getchar();
}
x=(f==1?x:-x);
}
template<typename T>
inline void write(T x){
if(x<0){
putchar('-');
x=-x;
}
if(x>=10){
write(x/10);
}
putchar(x%10+'0');
}
template<typename T>
inline void write_endl(T x){
write(x);
putchar('\n');
}
template<typename T>
inline void write_space(T x){
write(x);
putchar(' ');
}
}
using namespace IO;
const int N=2e5+10,S=2e6+10;
int n,m,cnt,idx=1,flag[N];
string s[N];
struct node{
int ch[26],fail;
}p[S];
void insert(string s,int id){
int now=1,len=s.size();
for(int i=0;i<len;i++){
if(!p[now].ch[s[i]-'a']){
p[now].ch[s[i]-'a']=++idx;
}
now=p[now].ch[s[i]-'a'];
}
flag[id]=now;
}
void get_fail(){
for(int i=0;i<26;i++){
p[0].ch[i]=1;
}
queue<int>q;
q.push(1);
p[1].fail=0;
while(q.size()){
int u=q.front();
q.pop();
for(int i=0;i<26;i++){
if(!p[u].ch[i]){
p[u].ch[i]=p[p[u].fail].ch[i];
}
else{
p[p[u].ch[i]].fail=p[p[u].fail].ch[i];
q.push(p[u].ch[i]);
}
}
}
}
vector<int>e[S];
int siz[S],dep[S],heavy[S],dfn[S],top[S],fa[S];
void make_tree(int u){
siz[u]=1;
for(auto v:e[u]){
dep[v]=dep[u]+1;
make_tree(v);
siz[u]+=siz[v];
fa[v]=u;
if(siz[v]>siz[heavy[u]]){
heavy[u]=v;
}
}
}
void dfs(int u,int topf){
dfn[u]=++dfn[0];
top[u]=topf;
if(heavy[u]){
dfs(heavy[u],topf);
}
for(auto v:e[u]){
if(v==heavy[u]){
continue;
}
dfs(v,v);
}
}
int lca(int u,int v){
while(top[u]!=top[v]){
if(dep[top[u]]<dep[top[v]]){
swap(u,v);
}
u=fa[top[u]];
}
if(dep[u]>dep[v]){
swap(u,v);
}
return u;
}
bool cmp(int x,int y){
return dfn[x]<dfn[y];
}
namespace Fenwick_Tree{
int c[S];
int lowbit(int x){
return x&(-x);
}
void update(int pos,int val){
while(pos<=idx){
c[pos]+=val;
pos+=lowbit(pos);
}
}
int query(int pos){
int res=0;
while(pos){
res+=c[pos];
pos-=lowbit(pos);
}
return res;
}
}
int pos[S];
void solve(){
read(n);
for(int i=1;i<=n;i++){
cin>>s[++cnt];
insert(s[cnt],cnt);
}
get_fail();
for(int i=2;i<=idx;i++){
e[p[i].fail].pb(i);
}
dep[1]=1;
make_tree(1);
dfs(1,1);
read(m);
while(m--){
int opt,x;
read(opt);
if(opt==1){
cin>>s[++cnt];
int now=1,len=s[cnt].size();
s[cnt]=' '+s[cnt];
for(int i=1;i<=len;i++){
now=p[now].ch[s[cnt][i]-'a'];
pos[i]=now;
}
sort(pos+1,pos+len+1,cmp);
for(int i=1;i<=len;i++){
Fenwick_Tree::update(dfn[pos[i]],1);
}
for(int i=1;i<len;i++){
Fenwick_Tree::update(dfn[lca(pos[i],pos[i+1])],-1);
}
}
else{
read(x);
int p=flag[x];
write_endl(Fenwick_Tree::query(dfn[p]+siz[p]-1)-Fenwick_Tree::query(dfn[p]-1));
}
}
}
signed main(){
#ifndef ONLINE_JUDGE
freopen("1.in","r",stdin);
freopen("1.out","w",stdout);
#endif
int t=1;
while(t--){
solve();
}
return 0;
}