图论 何钒佑
CF1477D Nezzar and Hidden Permutations
首先考虑将每一个数对 \((u,v)\) 看成一条 \(u\) 向 \(v\) 的连边。若此时有一个点 \(u\) 和其他点均有连边,则 \(p\) 中第 \(u\) 个位置上的数和 \(q\) 上第 \(u\) 个位置上的数一定相等,所以他们对答案一定都没有贡献,先将它们删去。然后我们发现剩下的位置一定可以让每个位置的 \(p\) 和 \(q\) 都相等。
现在考虑构造方案。发现此时的原图虽然没什么很好的性质,但原图的补图上没有孤点。所以我们考虑原图的补图。我们先考虑特殊情形大小\(\ge2\)菊花。我们可以这样构造,对于 \(p\) 让 \(p_{根节点}\) 等于 \(1\) 其余节点依次为 \(2\) ~ \(n\) ,对于 \(q\) 让 \(q_{根节点}\) 等于 \(n\) 其余节点依次等于 \(1\) ~ \(n-1\) 其中 \(p\) 的顺序和 \(q\) 的顺序一样。那么此时对于原图上存在的边,只会在这些叶子节点中,这些叶子节点的相对大小一定一样。
现在考虑一般情况,我们先在反图上求出一个任意的生成树森林,然后我们考虑对它进行"菊花链剖分",及分成若干个大小\(\ge2\) 的菊花,由于反图上没有孤点,所以一定可以分出来,具体过程如下:
先找到一个没有被划分好的节点 \(u\) 。
若与 \(u\) 相连的其它节点都没有被划分好,那么以这个节点为根,造出一个菊花。
否则考虑一个与 \(u\) 相连已经被分好的节点 \(v\) ,分两种情况讨论:
- \(v\) 所在的菊花大小大于 \(2\) ,那么由上面的划分方法可以得出 \(v\) 一定不是它所在菊花的根。所以可以直接将 \(u\) 和 \(v\) 化为同一个菊花,将任意一个节点设为根,注意要更新与根相连的节点。
- \(v\) 所在的菊花大小恰好为 \(2\) ,那么可以直接将 \(v\) 设为根,然后将 \(u\) 加入 \(v\) 的菊花之中。也要注意与根相连的节点。
这里还有一个问题,就是如何求出原图补图的一个生成树森林,我们可以维护一个还没有被加入森林的集合 \(S\) 然后对于每个节点 \(u\),遍历集合 \(S\) ,将不与 \(u\) 相连的加入森林。考虑时间复杂度的正确性。由于我们做多只会失配 \(m\) 次,其余的都会新加入一个节点,所以均摊下来是 \(O((n+m)log\space n)\) 的复杂度。
代码:
#include<bits/stdc++.h>
using namespace std;
#define il inline
struct IO
{
static const int Size=(1<<21);
char buf[Size],*p1,*p2;
int st[105],Top;
~IO(){clear();}
il void clear(){fwrite(buf,1,Top,stdout);Top=0;}
il char gc(){return p1==p2&&(p2=(p1=buf)+fread(buf,1,Size,stdin),p1==p2)?EOF:*p1++;}
il void pc(const char c){Top==Size&&(clear(),0);buf[Top++]=c;}
il IO& operator >>(char& c){while(c=gc(),c==' ' || c=='\n' || c=='\r');return *this;}
template<typename T>il IO& operator >>(T& x)
{
x=0;bool f=0;char c=gc();
while(!isdigit(c)){if(c=='-') f=1;c=gc();}
while(isdigit(c)){x=(x<<1)+(x<<3)+(c^48);c=gc();}
f?x=-x:0;
return *this;
}
il IO& operator >>(string& s)
{
s="";char c=gc();
while(c==' ' || c=='\n' || c=='\r') c=gc();
while(c!=' ' && c!='\n' && c!='\r' && c!=EOF) s+=c,c=gc();
return *this;
}
il IO& operator <<(const char c){pc(c);return *this;}
template<typename T> il IO& operator <<(T x)
{
if(x<0) pc('-'),x=-x;
do st[++st[0]]=x%10,x/=10;while(x);
while(st[0]) pc(st[st[0]--]+'0');
return *this;
}
il IO& operator <<(const string s){for(auto c:s) pc(c);return *this;}
il IO& operator <<(const char* c){for(int i=0;c[i];i++) pc(c[i]);return *this;}
} fin,fout;
const int N=500010;
int p[N],q[N],d[N],fl[N],tothua,rt[N],siz[N];
map<int,int> mp[N];
vector<int> hua[N];
vector<int> g[N];
set<int> st;
il void dfs(int u,int fa)
{
for(int v:st) if(mp[u].find(v)==mp[u].end()) g[u].push_back(v),g[v].push_back(u);
for(int v:g[u]) if(v!=fa) st.erase(v);
for(int v:g[u]) if(v!=fa) dfs(v,u);
}
il void get_hua(int u,int fa)
{
for(int v:g[u])
{
if(v!=fa) get_hua(v,u);
}
if(fl[u]) return;
int flag=1,idx=0;
for(int v:g[u])
{
if(fl[v]) flag=0,idx=v;
}
if(flag)
{
tothua++;fl[u]=tothua;siz[tothua]=1;rt[tothua]=u;
for(int v:g[u]) fl[v]=tothua,siz[tothua]++;
}
else
{
if(siz[fl[idx]]>2)
{
siz[fl[idx]]--;
tothua++;
siz[tothua]=2;
rt[tothua]=u;
fl[u]=fl[idx]=tothua;
for(int v:g[u])
{
if(!fl[v]) fl[v]=tothua,siz[tothua]++;
}
}
else
{
int www=fl[idx];
if(rt[www]!=idx)
{
rt[www]=idx;
for(int v:g[idx])
{
if(!fl[v]) siz[www]++,fl[v]=www;
}
}
}
}
}
signed main()
{
int t;fin>>t;
while(t--)
{
int n,m;fin>>n>>m;
for(int i=1;i<=n;i++) mp[i].clear(),d[i]=fl[i]=0,g[i].clear(),hua[i].clear();
for(int i=1;i<=m;i++)
{
int u,v;fin>>u>>v;
mp[u][v]=mp[v][u]=1;
d[u]++,d[v]++;
}
int now=n;
for(int i=1;i<=n;i++)
{
if(d[i]==n-1)
{
p[i]=q[i]=now;
now--;
}
else st.insert(i);
}
for(int i=1;i<=n;i++) if(st.find(i)!=st.end()) st.erase(st.find(i)),dfs(i,0);
tothua=0;
for(int i=1;i<=n;i++)
{
if(d[i]!=n-1 && !fl[i]) get_hua(i,0);
}
for(int i=1;i<=n;i++)
{
if(fl[i] && i!=rt[fl[i]]) hua[fl[i]].push_back(i);
}
for(int i=1;i<=n;i++)
{
if(fl[i] && i==rt[fl[i]])
{
p[i]=now,q[i]=now-siz[fl[i]]+1;
for(int j=0;j<(int)hua[fl[i]].size();j++)
{
p[hua[fl[i]][j]]=now-j-1;
q[hua[fl[i]][j]]=now-j;
}
now-=siz[fl[i]];
}
}
for(int i=1;i<=n;i++) fout<<p[i]<<" ";
fout<<"\n";
for(int i=1;i<=n;i++) fout<<q[i]<<" ";
fout<<"\n";
}
return 0;
}
/*
图上构造神题
*/
CF1634E Fair Share
图论建模好题。
我们考虑连接两个点表示它们不在同一个集合中。那我们可以考虑一种构造。就是同一个数组中相邻两个数连边,第 \(2\times n\) 次出现的数和上一次出现的同一个数连边。我们将第 \(1\) 种边称为横边,第二种称为纵边。我们发现每一个点都连出了一条纵边和一条横边,所以原图一定是若干个环,而每个环中一定是有若干条横边和纵边交替组成的,所以一定都是偶边,可以直接交替染色求出答案,原问题不合法当且仅当有一种数的出现次数为奇数。
代码:
#include<bits/stdc++.h>
using namespace std;
#define il inline
#define int long long
struct IO
{
static const int Size=(1<<21);
char buf[Size],*p1,*p2;
int st[105],Top;
~IO(){clear();}
il void clear(){fwrite(buf,1,Top,stdout);Top=0;}
il char gc(){return p1==p2&&(p2=(p1=buf)+fread(buf,1,Size,stdin),p1==p2)?EOF:*p1++;}
il void pc(const char c){Top==Size&&(clear(),0);buf[Top++]=c;}
il IO& operator >>(char& c){while(c=gc(),c==' ' || c=='\n' || c=='\r');return *this;}
template<typename T>il IO& operator >>(T& x)
{
x=0;bool f=0;char c=gc();
while(!isdigit(c)){if(c=='-') f=1;c=gc();}
while(isdigit(c)){x=(x<<1)+(x<<3)+(c^48);c=gc();}
f?x=-x:0;
return *this;
}
il IO& operator >>(string& s)
{
s="";char c=gc();
while(c==' ' || c=='\n' || c=='\r') c=gc();
while(c!=' ' && c!='\n' && c!='\r' && c!=EOF) s+=c,c=gc();
return *this;
}
il IO& operator <<(const char c){pc(c);return *this;}
template<typename T> il IO& operator <<(T x)
{
if(x<0) pc('-'),x=-x;
do st[++st[0]]=x%10,x/=10;while(x);
while(st[0]) pc(st[st[0]--]+'0');
return *this;
}
il IO& operator <<(const string s){for(auto c:s) pc(c);return *this;}
il IO& operator <<(const char* c){for(int i=0;c[i];i++) pc(c[i]);return *this;}
} fin,fout;
const int N=200010;
vector<int> ans[N];
struct node
{
int i,j;
} w[N];
int h[N],totedge=1,vis[N];
struct edge
{
int nxt,to;
} e[N<<1];
void add_edge(int u,int v)
{
totedge++;
e[totedge].to=v;
e[totedge].nxt=h[u];
h[u]=totedge;
}
map<int,int> lst;
void dfs(int u,int Lst)
{
vis[u]=1;
ans[w[u].i][w[u].j]=ans[w[Lst].i][w[Lst].j]^1;
for(int i=h[u];i;i=e[i].nxt)
{
int v=e[i].to;
if(vis[v]) continue;
dfs(v,u);
}
}
signed main()
{
int n;fin>>n;int tot=0;
for(int i=1;i<=n;i++)
{
int m;fin>>m;
for(int j=0;j<m;j++)
{
tot++;
w[tot]={i,j};
int a;fin>>a;
if(lst.find(a)==lst.end()) lst[a]=tot;
else add_edge(lst[a],tot),add_edge(tot,lst[a]),lst.erase(a);
if(j&1) add_edge(tot-1,tot),add_edge(tot,tot-1);
ans[i].push_back(-1);
}
}
if(!lst.empty())
{
fout<<"NO";
return 0;
}
ans[0].push_back(0);
for(int i=1;i<=tot;i++)
{
if(!vis[i]) dfs(i,0);
}
fout<<"YES\n";
for(int i=1;i<=n;i++)
{
for(int j:ans[i])
{
if(j==0) fout<<'L';
else fout<<'R';
}
fout<<"\n";
}
return 0;
}
/*
巧妙建边题,但不是流子
*/
CF843D Dynamic Shortest Path
由于这题时限很大,所以可以考虑 \(O(nq)\) 的做法。
\(dijskra\) 有两种方法一种是堆优化,复杂度带 \(log\),另一种叫分层 \(bfs\) ,是基于最短路大小的。考虑原题目,我们发现这题最短路增量很小,不超过 \(c\) ,可以像 \(Johnson\) 全源最短路算法一样求解最短路增量,可以用分层 \(bfs\) 优化,防止复杂度带 \(log\) 。一条边 \(u\rightarrow v\) 对 \(v\) 的贡献是 \(dis_u+add_u+w_{u\rightarrow v}-dis_v\) ,套用公式后直接更新就好了。初始要先求一遍最短路。
代码:
#include<bits/stdc++.h>
using namespace std;
#define il inline
#define ll long long
struct IO
{
static const int Size=(1<<21);
char buf[Size],*p1,*p2;
int st[105],Top;
~IO(){clear();}
il void clear(){fwrite(buf,1,Top,stdout);Top=0;}
il char gc(){return p1==p2&&(p2=(p1=buf)+fread(buf,1,Size,stdin),p1==p2)?EOF:*p1++;}
il void pc(const char c){Top==Size&&(clear(),0);buf[Top++]=c;}
il IO& operator >>(char& c){while(c=gc(),c==' ' || c=='\n' || c=='\r');return *this;}
template<typename T>il IO& operator >>(T& x)
{
x=0;bool f=0;char c=gc();
while(!isdigit(c)){if(c=='-') f=1;c=gc();}
while(isdigit(c)){x=(x<<1)+(x<<3)+(c^48);c=gc();}
f?x=-x:0;
return *this;
}
il IO& operator >>(string& s)
{
s="";char c=gc();
while(c==' ' || c=='\n' || c=='\r') c=gc();
while(c!=' ' && c!='\n' && c!='\r' && c!=EOF) s+=c,c=gc();
return *this;
}
il IO& operator <<(const char c){pc(c);return *this;}
template<typename T> il IO& operator <<(T x)
{
if(x<0) pc('-'),x=-x;
do st[++st[0]]=x%10,x/=10;while(x);
while(st[0]) pc(st[st[0]--]+'0');
return *this;
}
il IO& operator <<(const string s){for(auto c:s) pc(c);return *this;}
il IO& operator <<(const char* c){for(int i=0;c[i];i++) pc(c[i]);return *this;}
} fin,fout;
const int N=100010;
const ll inf=1e18;
queue<int> q[N];
int h[N],tot=1,vis[N],n,m,Q,add[N];
ll dis[N];
struct edge
{
int nxt,to,w;
} e[N];
void add_edge(int u,int v,int w)
{
tot++;
e[tot].to=v;
e[tot].w=w;
e[tot].nxt=h[u];
h[u]=tot;
}
void dijs()
{
priority_queue<pair<ll,int>,vector<pair<ll,int> >,greater<pair<ll,int> > > pq;
for(int i=1;i<=n;i++) dis[i]=inf;
dis[1]=0;pq.push({dis[1],1});
while(!pq.empty())
{
int u=pq.top().second;pq.pop();
if(vis[u]) continue;
vis[u]=1;
for(int i=h[u];i;i=e[i].nxt)
{
int v=e[i].to;
if(dis[v]>dis[u]+e[i].w)
{
dis[v]=dis[u]+e[i].w;
pq.push({dis[v],v});
}
}
}
}
signed main()
{
fin>>n>>m>>Q;
for(int i=1;i<=m;i++)
{
int u,v,w;fin>>u>>v>>w;
add_edge(u,v,w);
}
dijs();
while(Q--)
{
int op,c;fin>>op>>c;
if(op==1)
{
if(dis[c]==inf) fout<<-1<<"\n";
else fout<<dis[c]<<"\n";
}
else
{
for(int i=1;i<=c;i++)
{
int x;fin>>x;
e[x+1].w++;
}
for(int i=1;i<=n;i++) add[i]=c+1;
q[0].push(1);add[1]=0;
for(int i=0;i<=c;i++)
{
while(!q[i].empty())
{
int u=q[i].front();q[i].pop();
if(add[u]<i) continue;
for(int i=h[u];i;i=e[i].nxt)
{
int v=e[i].to,val=dis[u]+add[u]+e[i].w-dis[v];
if(val<add[v]) add[v]=val,q[val].push(v);
}
}
}
for(int i=1;i<=n;i++) if(add[i]!=c+1) dis[i]+=add[i];
}
}
return 0;
}
/*
分层bfs,之前没见过
*/
qoj#6354. 4
这题的前置题目是P1989 【模板】无向图三元环计数 - 洛谷,我们可以先考虑给无向图定向,度数小的向度数大的连有向边,度数一样就让编号小的连向编号大的,接下来我们来证明这样连边每个点的出度都不会大于 \(O(\sqrt m)\) ,分两种情况讨论:
1.原图上这点度数小于 \(O(\sqrt m)\) ,由于现在这张图的出度不会超过原图度数,所以该点出度不会超过 \(O(\sqrt m)\)
2.原图上这点度数大于 \(O(\sqrt m)\) ,那它连向的点 \(v\) 在原图上的度数都要大于 \(O(\sqrt m)\) ,由于总度数为 \(O(m)\) ,所以这样的点的个数不超过 \(O(\sqrt m)\) 。
然后我们考虑枚举三元环中的度数最小的点 \(u\) ,那么剩余两个点 \(v\) 和 \(w\) 一定在 \(w\) 的出点中,且两点之间有边,所以我们只需要枚举 \(u\) 的出点 \(v\) ,然后枚举 \(v\) 的出点 \(w\) ,验证 \(w\) 是不是 \(u\) 的出边连向的点。枚举 \(v\) 的复杂度是 \(O(m)\) ,枚举 \(w\) 的复杂度是 \(O(\sqrt m)\),所以总复杂度为 \(O(m \sqrt m)\)。
再考虑原题,依然是枚举 \(K4(u,v,w,x)\) 中的度数最小的点 \(u\) ,那么 \(v,w,x\) 一定都是 \(u\) 的出点,首先我们在 \(u\) 的出点中能和 \(u\) 构成三元环的一堆点 \((x,y)\) 之间连边,则现在我们需要在这些点中统计三元环个数。考虑这样做正确性,设我们取出的三元环是 \((v_1,v_2,v_3)\) ,则我们现在应有边 \(u\rightarrow v_1,u\rightarrow v_2,u\rightarrow v_3,v_1\rightarrow v_2,v_1\rightarrow v_3,v_2\rightarrow v_3\) ,它们合在一起刚刚好构成了一个 \(K4\) 。至于第二个三元环统计个数的方式,我们可以使用 \(bitset\) 维护枚举每条边时两个端点能到达的点的个数达到 \(O(\frac{边数\times 点数}{w})\) 的。带回原题目,边数总和不超过 \(O(m\sqrt m)\) ,每次参与运算的点数不超过 \(O(\sqrt m)\) ,所以总复杂度为 \(O(\frac{m^2}{w})\) ,可过。
代码:
#include<bits/stdc++.h>
using namespace std;
#define il inline
struct IO
{
static const int Size=(1<<21);
char buf[Size],*p1,*p2;
int st[105],Top;
~IO(){clear();}
il void clear(){fwrite(buf,1,Top,stdout);Top=0;}
il char gc(){return p1==p2&&(p2=(p1=buf)+fread(buf,1,Size,stdin),p1==p2)?EOF:*p1++;}
il void pc(const char c){Top==Size&&(clear(),0);buf[Top++]=c;}
il IO& operator >>(char& c){while(c=gc(),c==' ' || c=='\n' || c=='\r');return *this;}
template<typename T>il IO& operator >>(T& x)
{
x=0;bool f=0;char c=gc();
while(!isdigit(c)){if(c=='-') f=1;c=gc();}
while(isdigit(c)){x=(x<<1)+(x<<3)+(c^48);c=gc();}
f?x=-x:0;
return *this;
}
il IO& operator >>(string& s)
{
s="";char c=gc();
while(c==' ' || c=='\n' || c=='\r') c=gc();
while(c!=' ' && c!='\n' && c!='\r' && c!=EOF) s+=c,c=gc();
return *this;
}
il IO& operator <<(const char c){pc(c);return *this;}
template<typename T> il IO& operator <<(T x)
{
if(x<0) pc('-'),x=-x;
do st[++st[0]]=x%10,x/=10;while(x);
while(st[0]) pc(st[st[0]--]+'0');
return *this;
}
il IO& operator <<(const string s){for(auto c:s) pc(c);return *this;}
il IO& operator <<(const char* c){for(int i=0;c[i];i++) pc(c[i]);return *this;}
} fin,fout;
const int N=100010,M=500;
struct edge
{
int u,v;
} e[N];
bitset<M> b[M];
vector<int> g[N];
int vis[N],d[N],idx[N];
signed main()
{
int n,m;fin>>n>>m;
for(int i=1;i<=m;i++)
{
int u,v;fin>>u>>v;
d[u]++,d[v]++;
e[i]={u,v};
}
for(int i=1;i<=m;i++)
{
int u=e[i].u,v=e[i].v;
if(d[u]>d[v]) swap(u,v);
if(d[u]==d[v] && u>v) swap(u,v);
g[u].push_back(v);
}
long long ans=0;
for(int i=1;i<=n;i++)
{
int tmp=0;
for(int v:g[i])
{
vis[v]=i;
idx[v]=tmp,b[tmp]=0;
tmp++;
}
for(int x:g[i])
{
for(int w:g[x])
{
if(vis[w]==i)
{
int v=idx[x];
w=idx[w];
b[v][w]=b[w][v]=1;
ans+=(b[v]&b[w]).count();
}
}
}
}
fout<<ans;
return 0;
}
/*
妙妙性质题。
三元环计数可以先给边定向,然后暴力做就可以是对的。
四元团可以在三元环的基础上做
*/
P9257 [PA 2022] Mędrcy
拿到题可能有些不知所措。我们先找一下走掉的条件,下用 \(S_x\) 表示 \(x\) 所知道的咒语的集合:
- 若 \(x\) 在第一天走掉,则 \(S_x=\varnothing\) .
- 若 \(x\) 在第二天走掉,则存在 \(y\) 使得 \(S_x\cap S_y=\varnothing\) ,我们来分析一下这个式子的意思,它表示,若 \(x\) 现在拥有所有咒语,则 \(y\) 应该什么咒语都没有,所以应该在第一天离开,但是 \(y\) 第一天却没有走,所以一定有 \(x\) 不知道的咒语存在。就是在 \(x\) 的认知里,\(y\) 应该什么咒语都不知道。
- 若 \(x\) 在第三天走掉,则存在两个数 \(y,z\) 满足 \(S_x\cap S_y\cap S_z=\varnothing\) ,其中 \(S_y\cap S_z=\varnothing\) 为 \(y\) 和 \(z\) 在第二天走的条件,再与上一个 \(S_x\) 就表示此时的情形是在 \(x\) 认为自己知道所有咒语的情况下推出的。就是在 \(x\) 的认知里, \(y\) 和 \(z\) 应该在第二天就离开,此时他们却没离开,那么就说明有 \(x\) 不知道的咒语。
- 以此类推,若 \(x\) 在第 \(i\) 天走掉,则存在 \(i-1\) 个数,满足 \(S_x\cap S_{a_1}\cap S_{a_2} \cap \dots \cap S_{a_{i-1}}=\varnothing\) ,其中 \(S_{a_1}\cap S_{a_2} \cap \dots \cap S_{a_{i-1}}=\varnothing\) 为前 \(i-1\) 天的子问题,就是在 \(x\) 的认知里,这 \(i-1\) 个数应该在前 \(i-1\) 天离开,但事实并非如此,所以一定存在 \(x\) 不知道的咒语。
然后我们需要转化成 \(x\) 所不知道的集合 \(T_x\) 的形式,设全集为 \(U\),则若 \(x\) 在第 \(i\) 天走掉,应该存在 \(i-1\) 个数满足 \(T_x\cup T_{a_1}\cup T_{a_2}\cup\dots\cup T_{a_{i-1}}=U\) 。就是把上面的式子转化了一下。
如果我们将每一条咒语不知道的两个人连边,那我们发现我们实际上要求的就是这张图的最小点覆盖,而能在这一天走的人就是能在构成最小点覆盖的点集里的所有点。但一般图最小点覆盖是个著名的 \(NP-Hard\) 问题。当我们发现 \(k\) 很小,所以我们可以将原问题转化为不超过 \(i\) 个点能不能覆盖所有边。
我们可以每次找度数最大的点,分两种情况讨论,一种选它,一种不选它。如果选择了不选它,那么就要选与它相连的所有点当我们发现当最大点度数是一或二的情况时它的复杂度是指数级别的,所以我们需要将这类情况特判掉。当度数最大的点度数 \(\le 2\) 时,原图剩余的一定是若干条链和若干个环。
对于环,设它的长度是 \(len\),它的最小点覆盖大小是 \(\left\lceil \frac{len}{2}\right\rceil\) ,每一个点都可以成为最小点覆盖的点集所包含的点。
对于链,设它的长度为 \(len\) ,若 \(len\) 为奇数,那么此时的最小点覆盖为 \(\left\lfloor\frac{len}{2}\right\rfloor\) ,能够成为最小点覆盖点集所包含的点只有链上第 \(2,4,6,8\dots\) 等偶数位置上的点。若 \(len\) 为偶数,那么此时的最小点覆盖为 \(\frac{len}{2}\) ,每一个点都可以成为最小点覆盖点集所包含的点。
用 \(dfs\) 实现即可做到 \(T(k)=T(k-1)+T(k-3)\) 的 \(O(T(k)n)\) 的复杂度,当 \(k=30\) 时大概为 \(O(1e5n)\) 。
代码:
#include<bits/stdc++.h>
using namespace std;
#define il inline
#define int long long
struct IO
{
static const int Size=(1<<21);
char buf[Size],*p1,*p2;
int st[105],Top;
~IO(){clear();}
il void clear(){fwrite(buf,1,Top,stdout);Top=0;}
il char gc(){return p1==p2&&(p2=(p1=buf)+fread(buf,1,Size,stdin),p1==p2)?EOF:*p1++;}
il void pc(const char c){Top==Size&&(clear(),0);buf[Top++]=c;}
il IO& operator >>(char& c){while(c=gc(),c==' ' || c=='\n' || c=='\r');return *this;}
template<typename T>il IO& operator >>(T& x)
{
x=0;bool f=0;char c=gc();
while(!isdigit(c)){if(c=='-') f=1;c=gc();}
while(isdigit(c)){x=(x<<1)+(x<<3)+(c^48);c=gc();}
f?x=-x:0;
return *this;
}
il IO& operator >>(string& s)
{
s="";char c=gc();
while(c==' ' || c=='\n' || c=='\r') c=gc();
while(c!=' ' && c!='\n' && c!='\r' && c!=EOF) s+=c,c=gc();
return *this;
}
il IO& operator <<(const char c){pc(c);return *this;}
template<typename T> il IO& operator <<(T x)
{
if(x<0) pc('-'),x=-x;
do st[++st[0]]=x%10,x/=10;while(x);
while(st[0]) pc(st[st[0]--]+'0');
return *this;
}
il IO& operator <<(const string s){for(auto c:s) pc(c);return *this;}
il IO& operator <<(const char* c){for(int i=0;c[i];i++) pc(c[i]);return *this;}
} fin,fout;
const int N=1010;
int mp[N][N],mn,d[N],now,vis[N],fl[N],n;
vector<int> g[N],w;
bitset<N> ans,ch;
il void del(int u,int k)
{
for(int v:g[u]) d[v]-=k;
}
il void get_w(int u,int fa)
{
w.push_back(u);
fl[u]=1;
for(int v:g[u])
{
if(v==fa || vis[v] || fl[v]) continue;
get_w(v,u);
}
}
il void dfs()
{
if(now>mn) return;
int u=0;
for(int i=1;i<=n;i++)
{
if(!vis[i] && d[i]>d[u]) u=i;
}
if(!u)
{
if(now<mn) mn=now,ans=ch;
else if(now==mn) ans|=ch;
return;
}
if(d[u]>2)
{
vis[u]=ch[u]=1;
del(u,1);now++;
dfs();
vis[u]=ch[u]=0;
del(u,-1);now--;
vector<int> tmp;
for(int v:g[u])
{
if(!vis[v])
{
tmp.push_back(v);
vis[v]=ch[v]=1;
del(v,1);now++;
}
}
dfs();
for(int v:tmp)
{
vis[v]=ch[v]=0;
del(v,-1);now--;
}
}
else
{
for(int i=1;i<=n;i++) fl[i]=0;
for(int i=1;i<=n;i++)
{
if(!vis[i] && !fl[i] && d[i]==1)
{
w.clear();
get_w(i,0);
now+=w.size()/2;
if(w.size()&1)
{
for(int j=1;j<(int)w.size();j+=2) ch[w[j]]=1;
}
else for(int j:w) ch[j]=1;
}
}
for(int i=1;i<=n;i++)
{
if(!vis[i] && !fl[i])
{
w.clear();
get_w(i,0);
if(w.size()==1) continue;
now+=(w.size()+1)/2;
for(int j:w) ch[j]=1;
}
}
if(now<mn) mn=now,ans=ch;
else if(mn==now) ans|=ch;
for(int i=1;i<=n;i++) fl[i]=0;
for(int i=1;i<=n;i++)
{
if(!vis[i] && !fl[i] && d[i]==1)
{
w.clear();
get_w(i,0);
now-=w.size()/2;
if(w.size()&1)
{
for(int j=1;j<(int)w.size();j+=2) ch[w[j]]=0;
}
else for(int j:w) ch[j]=0;
}
}
for(int i=1;i<=n;i++)
{
if(!vis[i] && !fl[i])
{
w.clear();
get_w(i,0);
if(w.size()==1) continue;
now-=(w.size()+1)/2;
for(int j:w) ch[j]=0;
}
}
}
}
signed main()
{
int t;fin>>t;
while(t--)
{
int m,k;fin>>n>>m>>k;
for(int i=1;i<=n;i++) g[i].clear();
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
{
mp[i][j]=0;
}
}
for(int i=1;i<=m;i++)
{
int u,v;fin>>u>>v;
if(mp[u][v]) continue;
mp[u][v]=mp[v][u]=1;
g[u].push_back(v);
g[v].push_back(u);
}
for(int i=1;i<=n;i++) d[i]=g[i].size();
mn=k+1;dfs();
if(mn>k)
{
fout<<-1<<"\n";
continue;
}
fout<<mn<<" "<<ans.count()<<"\n";
for(int i=1;i<=n;i++) if(ans[i]) fout<<i<<" ";
fout<<"\n";
}
return 0;
}
/*
先转化题意,发现题目要求的就是最小点覆盖。
然后对于k暴力+剪枝就好了
*/
AT_agc036_d [AGC036D] Negative Cycle
我们先将原问题转化为差分约束模型,那么没有负环就等价于存在一组符合条件的 \(x_1\) ~ \(x_n\) 的解,我们可以将原问题转化为确定一组解,使得需要被删去的约束限制最少。突破口是 \(i\) 到 \(i+1\) 有一条边权为 \(0\) 的边,而这条边不可被删去,所以构造的解应满足 \(x_i>=x_{i+1}\) ,及应该是一个单调递减的序列,我们发现该序列一定是由若干相等的数所组成的连续段组成,所以我们可以对每一段连续段为基础做 \(dp\) 。但在这之前,我们发现相邻的两个连续段的差值一定为 \(1\) 因为如果差值为二,那么我们可以让后面所有段集体加一。这样只会少删一些从后向前的连边。那么此时需要删除的连边只会和前一个连续段和前一个的前一个连续段长度有关。
设 \(dp_{i,j,k}\) 表示现在在确定第 \(i\) 个数,当前数所在连续段长为 \(j\) ,再上一个连续段长度为 \(j\) ,转移可以分两种进行。
一种是 \(j\) 为 \(1\) 那么此时这个位置是当前连续段的第一个数,转移是:
虽然可以用前缀和优化一下,但没有必要。
第二种是 \(j\) 不为 \(1\) ,那转移的上一位是固定的,转移是:
都可以使用二位前缀和来优化一下 \(\sum A\) ,需要注意的是 \(k\) 是可以为 \(0\) 的,但此时要求 \(i=j\) ,其余情况一定存在上一个连续段。
代码:
#include<bits/stdc++.h>
using namespace std;
#define il inline
#define int long long
struct IO
{
static const int Size=(1<<21);
char buf[Size],*p1,*p2;
int st[105],Top;
~IO(){clear();}
il void clear(){fwrite(buf,1,Top,stdout);Top=0;}
il char gc(){return p1==p2&&(p2=(p1=buf)+fread(buf,1,Size,stdin),p1==p2)?EOF:*p1++;}
il void pc(const char c){Top==Size&&(clear(),0);buf[Top++]=c;}
il IO& operator >>(char& c){while(c=gc(),c==' ' || c=='\n' || c=='\r');return *this;}
template<typename T>il IO& operator >>(T& x)
{
x=0;bool f=0;char c=gc();
while(!isdigit(c)){if(c=='-') f=1;c=gc();}
while(isdigit(c)){x=(x<<1)+(x<<3)+(c^48);c=gc();}
f?x=-x:0;
return *this;
}
il IO& operator >>(string& s)
{
s="";char c=gc();
while(c==' ' || c=='\n' || c=='\r') c=gc();
while(c!=' ' && c!='\n' && c!='\r' && c!=EOF) s+=c,c=gc();
return *this;
}
il IO& operator <<(const char c){pc(c);return *this;}
template<typename T> il IO& operator <<(T x)
{
if(x<0) pc('-'),x=-x;
do st[++st[0]]=x%10,x/=10;while(x);
while(st[0]) pc(st[st[0]--]+'0');
return *this;
}
il IO& operator <<(const string s){for(auto c:s) pc(c);return *this;}
il IO& operator <<(const char* c){for(int i=0;c[i];i++) pc(c[i]);return *this;}
} fin,fout;
const int N=510,inf=1e18;
int a[N][N],sum1[N][N],sum2[N][N],dp[N][N][N];
signed main()
{
int n;fin>>n;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
{
if(i==j) continue;
fin>>a[i][j];
}
}
for(int i=1;i<=n;i++)
{
for(int j=1;j<i;j++)
{
sum1[i][j]=sum1[i-1][j]+sum1[i][j-1]-sum1[i-1][j-1]+a[i][j];
}
}
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
{
if(j>i) sum2[i][j]=sum2[i-1][j]+sum2[i][j-1]-sum2[i-1][j-1]+a[i][j];
else sum2[i][j]=sum2[i-1][j]+sum2[i][j-1]-sum2[i-1][j-1];
}
}
for(int i=0;i<=n;i++)
{
for(int j=0;j<=n;j++)
{
for(int k=0;k<=n;k++)
{
dp[i][j][k]=inf;
}
}
}
int ans=inf;
dp[1][1][0]=0;
for(int i=2;i<=n;i++)
{
dp[i][i][0]=dp[i-1][i-1][0]+sum2[i-1][i]-sum2[i-1][i-1];
for(int j=2;j<i;j++)
{
for(int k=1;k<=i-j;k++)
{
dp[i][j][k]=min(dp[i][j][k],dp[i-1][j-1][k]+sum2[i-1][i]-sum2[i-j][i]-(sum2[i-1][i-1]-sum2[i-j][i-1])+sum1[i][i-j-k]-sum1[i-1][i-j-k]);
}
}
for(int k=1;k<=i-1;k++)
{
for(int l=0;l<=i-1-k;l++)
{
dp[i][1][k]=min(dp[i][1][k],dp[i-1][k][l]+sum1[i][i-1-k]-sum1[i-1][i-1-k]);
}
}
}
for(int i=0;i<=n;i++)
{
for(int j=0;j<=n-i;j++)
{
ans=min(ans,dp[n][i][j]);
}
}
fout<<ans;
return 0;
}
/*
将原图转成差分约束模型
然后就可以总结出合法条件。
根据合法条件进行转化后跑dp
*/
P12434 [NERC2023] Cactus Transformation
This is a shit.I promise.
首先我们判断特殊情况,就是 \(m=\frac{3(n-1)}{2}\) 的情况,此时图由若干个三元环组成,不可以移动任何一条边,所以只需要判断两张图是否同构即可。
对于一般情况,一定合法,需要给出构造。首先我们需要一个中间状态,将两个仙人掌都转移到这一个中间状态,由于这个操作是可逆的,所以只需要将后面那一次的转换翻转过来接到第一次的答案后面就是最终答案了。至于这个中间状态,我们设为 \(1\) 的度数为 \(n-1\) 其他边的边连接的一定是 \((2,3),(4,5),(6,7)\dots\) 的类菊花图。应该长这样:

- 首先我们需要将原图拆为仅有三元环的图,即将一个大环拆为一个三元环和一些链,拆法如下。设原环为 \((a_1,a_2,a_3,\dots,a_k)\) 则我们可以将边 \(a_1\rightarrow a_k\) 改为 \(a_1\rightarrow a_3\) 那么原环就变为了一个三元环加一条链。如下图,断开 \(1\rightarrow 5\) ,连接 \(1\rightarrow 3\) :

-
然后我们可以对于一个与 \(1\) 相连的节点 \(u\) 查看他的子树内有没有三元环,如果有,将其中一条边断开,移到 \(1\) 处,如下图,断开 \(2\rightarrow 4\) 连接 \(1\rightarrow 5\) :

-
然后我们找到一条不与一相连且不在环上的边,断开它后连接 \(1\) 与这条边连接的两个点中距离 \(1\) 较远的点。如下图,断开 \(4\rightarrow 5\) 连接 \(1\rightarrow 4\) :

-
此时与 \(1\) 相连的,要么是一个单点,要么是一串三元环拼在一起,由于 \(m\ne \frac{3(n-1)}{2}\) ,所以一定会存在这个单点,设其中之一为 \(u\) ,那么我们可以断开一个与 \(1\) 相连的三元环,然后让其与 \(u\) 相连,这里要注意一下断开的边和与之连接的点,不能做无用操作,具体判断可以看代码。如下图,举个例子:

-
如果上面几步都无法操作了,那么此时的 \(1\) 的度数一定已经是 \(n-1\) 了,只需要调整 \(2\) ~ \(n\) 之间的连边就可以完成构造大业了,这个具体实现比较复杂一些,可以配合代码中的 \(zheng\_li()\) 函数食用。
因为判断现在该进行那一步比较困难,所以我们可以从第一步到第四步都执行一遍,无法执行就跳到下一步,这题实现非常充裕,随便写应该都可以过。
代码:
#include<bits/stdc++.h>
using namespace std;
#define il inline
struct IO
{
static const int Size=(1<<21);
char buf[Size],*p1,*p2;
int st[105],Top;
~IO(){clear();}
il void clear(){fwrite(buf,1,Top,stdout);Top=0;}
il char gc(){return p1==p2&&(p2=(p1=buf)+fread(buf,1,Size,stdin),p1==p2)?EOF:*p1++;}
il void pc(const char c){Top==Size&&(clear(),0);buf[Top++]=c;}
il IO& operator >>(char& c){while(c=gc(),c==' ' || c=='\n' || c=='\r');return *this;}
template<typename T>il IO& operator >>(T& x)
{
x=0;bool f=0;char c=gc();
while(!isdigit(c)){if(c=='-') f=1;c=gc();}
while(isdigit(c)){x=(x<<1)+(x<<3)+(c^48);c=gc();}
f?x=-x:0;
return *this;
}
il IO& operator >>(string& s)
{
s="";char c=gc();
while(c==' ' || c=='\n' || c=='\r') c=gc();
while(c!=' ' && c!='\n' && c!='\r' && c!=EOF) s+=c,c=gc();
return *this;
}
il IO& operator <<(const char c){pc(c);return *this;}
template<typename T> il IO& operator <<(T x)
{
if(x<0) pc('-'),x=-x;
do st[++st[0]]=x%10,x/=10;while(x);
while(st[0]) pc(st[st[0]--]+'0');
return *this;
}
il IO& operator <<(const string s){for(auto c:s) pc(c);return *this;}
il IO& operator <<(const char* c){for(int i=0;c[i];i++) pc(c[i]);return *this;}
} fin,fout;
const int N=1010;
int n,m;
struct XXX
{
set<int> g[N];
vector<pair<pair<int,int>,pair<int,int> > > ans;
int f[N],d[N],vis[N],dong,son[N],fl[N],to[N];
il void upd(int u,int v,int x,int y)
{
g[u].erase(v);
g[v].erase(u);
g[x].insert(y);
g[y].insert(x);
dong=1;
ans.push_back({{u,v},{x,y}});
}
il void inp()
{
for(int i=1;i<=m;i++)
{
int u,v;fin>>u>>v;
g[u].insert(v);
g[v].insert(u);
}
}
il void chai_huan(int u,int fa)
{
if(dong) return;
f[u]=fa;d[u]=d[fa]+1;
vis[u]=1;
for(int v:g[u])
{
if(v==fa) continue;
if(vis[v])
{
if(d[v]<d[u]) continue;
if(d[v]-d[u]>=3)
{
upd(u,v,v,f[f[v]]);
return;
}
continue;
}
chai_huan(v,u);
}
}
il void get_son(int u,int fa)
{
f[u]=fa;d[u]=d[fa]+1;
vis[u]=1;
for(int v:g[u])
{
if(v==fa) continue;
if(vis[v])
{
if(d[v]>d[u]) continue;
son[u]=u;fl[u]=fl[f[u]]=1;
continue;
}
get_son(v,u);
if(!son[u]) son[u]=son[v];
if(son[u] && son[v] && d[son[u]]>d[son[v]]) son[u]=son[v];
}
}
il void get_huan(int u,int fa)
{
f[u]=fa;d[u]=d[fa]+1;
vis[u]=1;
for(int v:g[u])
{
if(v==fa) continue;
if(vis[v])
{
if(d[v]>d[u]) continue;
fl[u]=fl[f[u]]=1;
to[u]=f[u];to[f[u]]=u;
continue;
}
get_huan(v,u);
}
}
il void zheng_li()
{
for(int i=1;i<=n;i++) fl[i]=to[i]=vis[i]=0;
get_huan(1,0);int idx=0;
for(int i=2;i<=n;i++) if(!fl[i]) idx=i;
set<pair<int,int> > tmp;
int cnt=0;
for(int i=2;i<=n;i++)
{
if(fl[i] && i<to[i])
{
tmp.insert({i,to[i]});
cnt++;
}
}
for(int i=1;i<=cnt;i++)
{
int x=i*2,y=i*2+1;
if(fl[x])
{
if(fl[y])
{
if(to[x]==y)
{
tmp.erase(tmp.begin());
continue;
}
upd(y,to[y],to[y],idx);
to[to[y]]=idx,to[idx]=to[y];
fl[idx]=fl[to[y]]=1;
tmp.erase(tmp.begin());
tmp.erase(tmp.begin());
if(to[y]<idx) tmp.insert({to[y],idx});
else tmp.insert({idx,to[y]});
upd(x,to[x],x,y);
idx=to[x];fl[idx]=0;
to[x]=y,to[y]=x;
}
else
{
tmp.erase(tmp.begin());
idx=to[x];fl[idx]=to[idx]=0;
upd(x,to[x],x,y);
fl[y]=fl[x]=1;to[x]=y,to[y]=x;
}
}
else
{
if(fl[y])
{
tmp.erase(tmp.begin());
idx=to[y];fl[idx]=to[idx]=0;
upd(y,to[y],x,y);
fl[x]=fl[y]=1;to[x]=y,to[y]=x;
}
else
{
auto t=*tmp.begin();
tmp.erase(tmp.begin());
upd(t.first,t.second,x,t.first);
upd(x,t.first,x,y);
to[t.first]=to[t.second]=0,fl[t.first]=fl[t.second]=0;
to[x]=y,to[y]=x,fl[x]=fl[y]=1;
}
}
}
}
il void solve()
{
while(1)
{
if((int)g[1].size()==n-1)
{
zheng_li();
return;
}
dong=0;
for(int i=1;i<=n;i++) vis[i]=0;
chai_huan(1,0);
if(dong) continue;
for(int i=1;i<=n;i++) vis[i]=son[i]=fl[i]=0;
get_son(1,0);
for(int u:g[1])
{
if(d[u]==2)
{
if(d[son[u]]>=4)
{
int v=son[u],tmp=v;
while(d[tmp]>=4) tmp=f[tmp];
upd(v,f[f[v]],tmp,1);
break;
}
}
}
if(dong) continue;
for(int i=2;i<=n;i++)
{
if(!fl[i] && d[i]>=3)
{
upd(i,f[i],i,1);
break;
}
}
if(dong) continue;
int idx=0;
for(int u:g[1])
{
if(g[u].size()==1)
{
idx=u;
break;
}
}
for(int u:g[1])
{
if(d[u]==2)
{
if(d[son[u]]==3)
{
int v=son[u];
if(g[v].size()!=2)
{
upd(v,u,u,idx);
break;
}
else if(g[u].size()!=2)
{
upd(v,u,v,idx);
break;
}
}
}
}
}
}
} A,B;
signed main()
{
fin>>n>>m;A.inp();B.inp();
if(n%2==1 && m==3*(n-1)/2)
{
int fl=1;
for(int i=1;i<=n;i++)
{
if(A.g[i].size()!=B.g[i].size())
{
fl=0;
break;
}
auto tmp1=A.g[i].begin(),tmp2=B.g[i].begin();
while(tmp1!=A.g[i].end())
{
if((*tmp1)!=(*tmp2))
{
fl=0;
break;
}
tmp1++,tmp2++;
}
if(!fl) break;
}
if(fl) fout<<"YES\n0";
else fout<<"NO";
return 0;
}
fout<<"YES\n";
A.solve();B.solve();reverse(B.ans.begin(),B.ans.end());
fout<<A.ans.size()+B.ans.size()<<"\n";
for(auto i:A.ans) fout<<i.first.first<<" "<<i.first.second<<" "<<i.second.first<<" "<<i.second.second<<"\n";
for(auto i:B.ans) fout<<i.second.first<<" "<<i.second.second<<" "<<i.first.first<<" "<<i.first.second<<"\n";
return 0;
}
CF1519F Chests and Keys
胸和钥匙???
首先我们可以考虑当每个箱子上的锁确定时, \(Bob\) 的最优收益,这是一个比较经典的最小割建模,让源点向每个箱子连边,边权为 \(a_i\) ,然后让每个锁向汇点连边,边权为 \(b_i\) ,然后让箱子向这个箱子上的锁连边,边权为 \(inf\) ,\(Bob\) 所取得的最大收益就是 \(\sum a_i\) 减去这张图的最小割。为了让 \(Bob\) 不能取得正收益,原图最小割一定是 \(\sum a_i\) 即,源点连向箱子的所有边都要满流。
所以我们就可以用 \(dp\) 来计算答案的最小值了。\(dp_{i,j,k,s}\) 表示现在考虑到了第 \(i\) 个箱子,第 \(j\) 个锁,源点连向该箱子的边剩余流量为 \(k\) ,所有钥匙向汇点连边的剩余流量状态为 \(s\) ,其中 \(s\) 是一个 \(m\) 位的五进制数。转移时就是有流量就需要加上这条边的权值,没有就不需要。复杂度 \(O(nmab^m)\) 。这题的每一个变量都很小,可过。
代码:
#include<bits/stdc++.h>
using namespace std;
#define il inline
#define int long long
struct IO
{
static const int Size=(1<<21);
char buf[Size],*p1,*p2;
int st[105],Top;
~IO(){clear();}
il void clear(){fwrite(buf,1,Top,stdout);Top=0;}
il char gc(){return p1==p2&&(p2=(p1=buf)+fread(buf,1,Size,stdin),p1==p2)?EOF:*p1++;}
il void pc(const char c){Top==Size&&(clear(),0);buf[Top++]=c;}
il IO& operator >>(char& c){while(c=gc(),c==' ' || c=='\n' || c=='\r');return *this;}
template<typename T>il IO& operator >>(T& x)
{
x=0;bool f=0;char c=gc();
while(!isdigit(c)){if(c=='-') f=1;c=gc();}
while(isdigit(c)){x=(x<<1)+(x<<3)+(c^48);c=gc();}
f?x=-x:0;
return *this;
}
il IO& operator >>(string& s)
{
s="";char c=gc();
while(c==' ' || c=='\n' || c=='\r') c=gc();
while(c!=' ' && c!='\n' && c!='\r' && c!=EOF) s+=c,c=gc();
return *this;
}
il IO& operator <<(const char c){pc(c);return *this;}
template<typename T> il IO& operator <<(T x)
{
if(x<0) pc('-'),x=-x;
do st[++st[0]]=x%10,x/=10;while(x);
while(st[0]) pc(st[st[0]--]+'0');
return *this;
}
il IO& operator <<(const string s){for(auto c:s) pc(c);return *this;}
il IO& operator <<(const char* c){for(int i=0;c[i];i++) pc(c[i]);return *this;}
} fin,fout;
const int N=10,inf=1e18;
int a[N],b[N],c[N][N],dp[7][7][5][15630],pw5[10];
signed main()
{
int n,m;fin>>n>>m;
for(int i=1;i<=n;i++) fin>>a[i];
for(int i=1;i<=m;i++) fin>>b[i];
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++) fin>>c[i][j];
}
memset(dp,0x3f,sizeof(dp));
for(int i=0;i<=m;i++) pw5[i]=pow(5,i);
int tmp=0;
for(int i=1;i<=m;i++) tmp+=pw5[i-1]*b[i];
dp[0][m][0][tmp]=0;
for(int i=1;i<=n;i++)
{
for(int j=0;j<=tmp;j++) dp[i][0][a[i]][j]=dp[i-1][m][0][j];
for(int j=1;j<=m;j++)
{
for(int k=0;k<=a[i];k++)
{
for(int s=0;s<=tmp;s++)
{
for(int l=0;l<=a[i]-k && l<=b[j]-(s/pw5[j-1])%5;l++)
{
dp[i][j][k][s]=min(dp[i][j][k][s],dp[i][j-1][k+l][s+pw5[j-1]*l]+(l?c[i][j]:0));
}
}
}
}
}
int ans=inf;
for(int s=0;s<=tmp;s++) ans=min(ans,dp[n][m][0][s]);
if(ans!=inf) fout<<ans;
else fout<<-1;
return 0;
}
/*
先对于Bob网络流建模求最大能获得的值
再看Alice能胜利可以满足的条件。
*/
AT_agc037_d [AGC037D] Sorting a Grid
由于初始状态 \(A\) 网格和终止状态 \(D\) 网格都是定的,所以 \(B\) 和 \(C\) 中每一行的元素集合都是定的。那么我们只需要确定 \(B\) 和 \(C\) 的列号即可,我们考虑从第一列到第 \(M\) 列依次确定,我们发现每一行都需要匹配一次 \(B\) 矩阵和 \(C\) 矩阵这一列的元素,匹配好后删掉当前的匹配边,这张匹配的图长的样子是分左中右三部分,左边是矩阵 \(B\) 的行号,右边是矩阵 \(C\) 的行号,中间是数值,左部点和中间点的连边是这一行含有这些点,右部点同理。然后源点向左部点连边,右部点向汇点连边跑 \(M\) 轮匹配即可。
代码:
#include<bits/stdc++.h>
using namespace std;
#define il inline
#define int long long
struct IO
{
static const int Size=(1<<21);
char buf[Size],*p1,*p2;
int st[105],Top;
~IO(){clear();}
il void clear(){fwrite(buf,1,Top,stdout);Top=0;}
il char gc(){return p1==p2&&(p2=(p1=buf)+fread(buf,1,Size,stdin),p1==p2)?EOF:*p1++;}
il void pc(const char c){Top==Size&&(clear(),0);buf[Top++]=c;}
il IO& operator >>(char& c){while(c=gc(),c==' ' || c=='\n' || c=='\r');return *this;}
template<typename T>il IO& operator >>(T& x)
{
x=0;bool f=0;char c=gc();
while(!isdigit(c)){if(c=='-') f=1;c=gc();}
while(isdigit(c)){x=(x<<1)+(x<<3)+(c^48);c=gc();}
f?x=-x:0;
return *this;
}
il IO& operator >>(string& s)
{
s="";char c=gc();
while(c==' ' || c=='\n' || c=='\r') c=gc();
while(c!=' ' && c!='\n' && c!='\r' && c!=EOF) s+=c,c=gc();
return *this;
}
il IO& operator <<(const char c){pc(c);return *this;}
template<typename T> il IO& operator <<(T x)
{
if(x<0) pc('-'),x=-x;
do st[++st[0]]=x%10,x/=10;while(x);
while(st[0]) pc(st[st[0]--]+'0');
return *this;
}
il IO& operator <<(const string s){for(auto c:s) pc(c);return *this;}
il IO& operator <<(const char* c){for(int i=0;c[i];i++) pc(c[i]);return *this;}
} fin,fout;
const int N=110,inf=1e9;
int h[N*N],totedge=1,cnt,ansf,dis[N],a[N][N],b[N][N],c[N][N],cur[N*N],S,T;
struct node
{
int to,nxt,w;
} e[N*N<<2];
void add(int u,int v,int w)
{
totedge++;
e[totedge].nxt=h[u];
e[totedge].to=v;
e[totedge].w=w;
h[u]=totedge;
}
void add_edge(int u,int v,int w)
{
add(u,v,w),add(v,u,0);
}
bool bfs()
{
queue<int> q;
for(int i=1;i<=cnt;i++) dis[i]=0,cur[i]=h[i];
dis[S]=1;q.push(S);
while(!q.empty())
{
int u=q.front();q.pop();
for(int i=h[u];i;i=e[i].nxt)
{
int v=e[i].to;
if(e[i].w && !dis[v])
{
dis[v]=dis[u]+1;
q.push(v);
}
}
}
return dis[T]>0;
}
int dfs(int u,int val)
{
if(u==T) return val;
int w=0;
for(int &i=cur[u];i;i=e[i].nxt)
{
int v=e[i].to;
if(dis[v]==dis[u]+1 && e[i].w>0)
{
int x=dfs(v,min(e[i].w,val-w));
if(x>0) w+=x,e[i].w-=x,e[i^1].w+=x;
if(w==val) return w;
}
}
return w;
}
void dinic()
{
while(bfs()) ansf+=dfs(S,inf);
}
signed main()
{
int n,m;fin>>n>>m;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
fin>>a[i][j];
}
}
cnt=n*m+n*2+2,S=cnt-1,T=cnt;
for(int i=1;i<=n;i++)
{
add_edge(S,i+n*m,1);
add_edge(i+n*m+n,T,1);
for(int j=1;j<=m;j++) add_edge(i+n*m,a[i][j],1),add_edge(m*(i-1)+j,i+n*m+n,1);
}
for(int i=1;i<=m;i++)
{
dinic();
for(int j=1;j<=n;j++)
{
for(int k=h[j+n*m];k;k=e[k].nxt)
{
int v=e[k].to;
if(v==S)
{
e[k].w=0;
e[k^1].w=1;
}
else if(e[k^1].w)
{
e[k^1].w=0;
b[j][i]=v;
}
}
for(int k=h[j+n*m+n];k;k=e[k].nxt)
{
int v=e[k].to;
if(v==T)
{
e[k].w=1;
e[k^1].w=0;
}
else if(e[k].w)
{
e[k].w=0;
c[j][i]=v;
}
}
}
}
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++) fout<<b[i][j]<<" ";
fout<<"\n";
}
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++) fout<<c[i][j]<<" ";
fout<<"\n";
}
return 0;
}
CF213E Two Permutations
首先可以将 \(P\) 和 \(Q\) 都拆成置换形式,设 \(i\) 在 \(P\) 中所在置换环编号为 \(p_i\) ,在 \(Q\) 中所在置换环编号为 \(q_i\) ,我们发现每个置换环要么拆为若干个自环,要么不拆。对于每一个 \(P\) 中的置换环,若拆开则是源点向该置换环,若不拆则该置换环向汇点连边。 \(Q\) 中的置换环则是若拆开则该置换环向汇点连边,若不拆则是源点向该置换环。然后对于每一个 \(i\) 做如下分类讨论建图跑最小割:
- \(P_i=Q_i=i\) ,无论拆还是不拆答案都会减一。
- \(P_i=Q_i\ne i\) ,若都不拆或都拆答案减一,否则答案不减。 \(p_i\) 和 \(q_i\) 互相连边。
- \(P_i=i,Q_i\ne i\),若拆 \(q_i\) 答案减一,否则不减,源点向 \(q_i\) 连边。
- \(Q_i=i,P_i\ne i\),若拆 \(p_i\) 答案减一,否则不减,\(p_i\) 向汇点连边。
- \(P_i\ne Q_i,P_i\ne i,Q_i\ne i\) ,若两个都拆则答案减一,否则不减, \(p_i\) 向 \(q_i\) 连边。
答案就是 \(n\) 减此图最小割。
代码:
#include<bits/stdc++.h>
using namespace std;
#define il inline
struct IO
{
static const int Size=(1<<21);
char buf[Size],*p1,*p2;
int st[105],Top;
~IO(){clear();}
il void clear(){fwrite(buf,1,Top,stdout);Top=0;}
il char gc(){return p1==p2&&(p2=(p1=buf)+fread(buf,1,Size,stdin),p1==p2)?EOF:*p1++;}
il void pc(const char c){Top==Size&&(clear(),0);buf[Top++]=c;}
il IO& operator >>(char& c){while(c=gc(),c==' ' || c=='\n' || c=='\r');return *this;}
template<typename T>il IO& operator >>(T& x)
{
x=0;bool f=0;char c=gc();
while(!isdigit(c)){if(c=='-') f=1;c=gc();}
while(isdigit(c)){x=(x<<1)+(x<<3)+(c^48);c=gc();}
f?x=-x:0;
return *this;
}
il IO& operator >>(string& s)
{
s="";char c=gc();
while(c==' ' || c=='\n' || c=='\r') c=gc();
while(c!=' ' && c!='\n' && c!='\r' && c!=EOF) s+=c,c=gc();
return *this;
}
il IO& operator <<(const char c){pc(c);return *this;}
template<typename T> il IO& operator <<(T x)
{
if(x<0) pc('-'),x=-x;
do st[++st[0]]=x%10,x/=10;while(x);
while(st[0]) pc(st[st[0]--]+'0');
return *this;
}
il IO& operator <<(const string s){for(auto c:s) pc(c);return *this;}
il IO& operator <<(const char* c){for(int i=0;c[i];i++) pc(c[i]);return *this;}
} fin,fout;
const int N=100010,inf=1e9;
int p[N],q[N],visp[N],visq[N],cntp,cntq,cnt,tot=1,h[N<<1],cur[N<<1],S,T,ans,dis[N<<1];
struct edge
{
int to,nxt,w;
} e[N<<2];
il void add(int u,int v,int w)
{
tot++;
e[tot].w=w;
e[tot].nxt=h[u];
e[tot].to=v;
h[u]=tot;
}
il void add_edge(int u,int v,int w)
{
add(u,v,w);add(v,u,0);
}
il bool bfs()
{
queue<int> q;
for(int i=1;i<=cnt;i++) dis[i]=0,cur[i]=h[i];
dis[S]=1;q.push(S);
while(!q.empty())
{
int u=q.front();q.pop();
for(int i=h[u];i;i=e[i].nxt)
{
int v=e[i].to;
if(e[i].w && !dis[v])
{
dis[v]=dis[u]+1;
q.push(v);
}
}
}
return dis[T]>0;
}
il int dfs(int u,int val)
{
if(u==T) return val;
int w=0;
for(int &i=cur[u];i;i=e[i].nxt)
{
int v=e[i].to;
if(dis[v]==dis[u]+1 && e[i].w>0)
{
int x=dfs(v,min(e[i].w,val-w));
if(x>0) w+=x,e[i].w-=x,e[i^1].w+=x;
if(w==val) return val;
}
}
return w;
}
il void Dinic()
{
while(bfs()) ans-=dfs(S,inf);
}
signed main()
{
int n;fin>>n;
for(int i=1;i<=n;i++) fin>>p[i],p[i]++;
for(int i=1;i<=n;i++) fin>>q[i],q[i]++;
for(int i=1;i<=n;i++)
{
if(!visp[i])
{
cntp++;
int now=i;
while(1)
{
visp[now]=cntp;
now=p[now];
if(now==i) break;
}
}
}
for(int i=1;i<=n;i++)
{
if(!visq[i])
{
cntq++;
int now=i;
while(1)
{
visq[now]=cntq;
now=q[now];
if(now==i) break;
}
}
}
ans=n;
cnt=cntp+cntq+2;S=cnt-1,T=cnt;
for(int i=1;i<=n;i++)
{
if(p[i]==q[i])
{
if(p[i]==i) ans--;
else add_edge(visp[i],visq[i]+cntp,1),add_edge(visq[i]+cntp,visp[i],1);
}
else
{
if(p[i]==i) add_edge(S,visq[i]+cntp,1);
else if(q[i]==i) add_edge(visp[i],T,1);
else add_edge(visp[i],visq[i]+cntp,1);
}
}
Dinic();
fout<<ans;
return 0;
}
/*
考虑将排列表示成置换形式。
然后发现同一个置换环里的只能拆分成若干自环或不动。
然后第i位对于p[i]和q[i]分讨一下就好了。
*/
CFGYM103855I. Marbles
考虑图论建模。第一个操作是将 \(i\) 和 \(j\) 所在袋子连向一个新的袋子,边权限制为 \([0,inf]\) ,第二个操作是将 \(i\) 所在的袋子连向 \(i\) 边权限制为 \([0,1]\) ,第三个操作是将 \(i\) 所在的袋子联想一个新的袋子,边权限制为 \([l,r]\) ,最后把所有弹珠都丢掉,然后跑上下界可行流即可。
代码:
#include<bits/stdc++.h>
using namespace std;
#define il inline
#define int long long
struct IO
{
static const int Size=(1<<21);
char buf[Size],*p1,*p2;
int st[105],Top;
~IO(){clear();}
il void clear(){fwrite(buf,1,Top,stdout);Top=0;}
il char gc(){return p1==p2&&(p2=(p1=buf)+fread(buf,1,Size,stdin),p1==p2)?EOF:*p1++;}
il void pc(const char c){Top==Size&&(clear(),0);buf[Top++]=c;}
il IO& operator >>(char& c){while(c=gc(),c==' ' || c=='\n' || c=='\r');return *this;}
template<typename T>il IO& operator >>(T& x)
{
x=0;bool f=0;char c=gc();
while(!isdigit(c)){if(c=='-') f=1;c=gc();}
while(isdigit(c)){x=(x<<1)+(x<<3)+(c^48);c=gc();}
f?x=-x:0;
return *this;
}
il IO& operator >>(string& s)
{
s="";char c=gc();
while(c==' ' || c=='\n' || c=='\r') c=gc();
while(c!=' ' && c!='\n' && c!='\r' && c!=EOF) s+=c,c=gc();
return *this;
}
il IO& operator <<(const char c){pc(c);return *this;}
template<typename T> il IO& operator <<(T x)
{
if(x<0) pc('-'),x=-x;
do st[++st[0]]=x%10,x/=10;while(x);
while(st[0]) pc(st[st[0]--]+'0');
return *this;
}
il IO& operator <<(const string s){for(auto c:s) pc(c);return *this;}
il IO& operator <<(const char* c){for(int i=0;c[i];i++) pc(c[i]);return *this;}
} fin,fout;
const int N=2010,inf=1e9;
int cnt,tot,f[N],vis[N],fl[N],d[N<<2],totedge=1,ans[N],SS,TT,ansf,h[N<<2],cur[N<<2],dis[N<<2];
struct node
{
int u,v,l,r;
} E[N<<3];
int fnd(int x)
{
if(f[x]==x) return x;
return f[x]=fnd(f[x]);
}
struct edge
{
int nxt,to,w,idx;
} e[N<<5];
void add(int u,int v,int w,int idx)
{
totedge++;
e[totedge].to=v;
e[totedge].w=w;
e[totedge].idx=idx;
e[totedge].nxt=h[u];
h[u]=totedge;
}
void add_edge(int u,int v,int w,int idx)
{
add(u,v,w,idx);add(v,u,0,idx);
}
bool bfs()
{
queue<int> q;
for(int i=1;i<=tot;i++) dis[i]=0,cur[i]=h[i];
dis[SS]=1;q.push(SS);
while(!q.empty())
{
int u=q.front();q.pop();
for(int i=h[u];i;i=e[i].nxt)
{
int v=e[i].to;
if(e[i].w && !dis[v])
{
dis[v]=dis[u]+1;
q.push(v);
}
}
}
return dis[TT]>0;
}
int dfs(int u,int val)
{
if(u==TT) return val;
int w=0;
for(int& i=cur[u];i;i=e[i].nxt)
{
int v=e[i].to;
if(e[i].w && dis[v]==dis[u]+1)
{
int x=dfs(v,min(val-w,e[i].w));
if(x>0) w+=x,e[i].w-=x,e[i^1].w+=x;
if(w==val) return w;
}
}
return w;
}
void dinic()
{
while(bfs()) ansf+=dfs(SS,inf);
}
signed main()
{
int n,m;fin>>n>>m;
for(int i=1;i<=n;i++) f[i]=i,vis[i]=i+n;
tot=2*n;
for(int i=1;i<=n;i++) E[++cnt]={i,i+n,0,1};
while(m--)
{
int op;fin>>op;
if(op==1)
{
int u,v;fin>>u>>v;
u=fnd(u),v=fnd(v);
tot++;
E[++cnt]={vis[u],tot,0,n};
E[++cnt]={vis[v],tot,0,n};
f[v]=u;vis[u]=tot;
}
else if(op==2)
{
int u;fin>>u;int fu=fnd(u);
E[++cnt]={vis[fu],u,0,1};
fl[u]=1;
}
else
{
int u,l,r;fin>>u>>l>>r;
tot++;u=fnd(u);
E[++cnt]={vis[u],tot,l,r};
vis[u]=tot;
}
}
for(int i=1;i<=n;i++)
{
if(!fl[i])
{
int fu=fnd(i);
E[++cnt]={vis[fu],i,0,1};
}
}
tot+=2;SS=tot-1,TT=tot;
for(int i=1;i<=cnt;i++)
{
d[E[i].v]+=E[i].l,d[E[i].u]-=E[i].l;
if(i<=n)
{
ans[i]=E[i].l;
add_edge(E[i].u,E[i].v,E[i].r-E[i].l,i);
}
else add_edge(E[i].u,E[i].v,E[i].r-E[i].l,n+1);
}
for(int i=1;i<=tot-2;i++)
{
if(d[i]>0) add_edge(SS,i,d[i],n+1);
else if(d[i]<0) add_edge(i,TT,-d[i],n+1);
}
dinic();
for(int i=h[SS];i;i=e[i].nxt)
{
if(e[i].w)
{
fout<<"NO\n";
return 0;
}
}
fout<<"YES\n";
for(int i=3;i<=totedge;i+=2) ans[e[i].idx]+=e[i].w;
for(int i=1;i<=n;i++) fout<<(ans[i]?'R':'B');
return 0;
}
/*
图论建模,合并两个包视为新建一个节点
要求范围相当于新建一个节点连向它时有边权限制。
丢失相当于连回去变成环。
*/
P4694 [PA 2013] Raper
模拟费用流板子题。首先这题的建模非常好想,就是源点向 \(n\) 个点连边,流量限制为 \(1\) ,费用为 \(a_i\),\(n\) 个点向汇点连边,流量限制为 \(1\) ,费用为 \(b_i\),然后第 \(i\) 个点向第 \(i+1\) 个点连边。总流量应为 \(k\) ,直接跑费用流肯定是不行的,所以我们考虑用线段树模拟费用流。
我们选择的一个 \(a_i\) ,\(b_j\) 的 \(i\) 和 \(j\) 的关系有两种,一种是 \(i\le j\) ,另一种是 \(i>j\) ,对于 \(i\le j\) 的情况,它对原图反边上的流量没什么限制,只会让 \([i,j)\) 上的流量 \(+1\) ,对于 \(i>j\) 的情况要求 \([j,i)\) 上的流量 \(>0\) ,然后会让 \([j,i)\) 上的流量 \(-1\) ,由于这个流量 \(>0\) 的信息很难维护,所以我们可以考虑加入一个流量始终为 \(0\) 的 \(n\) 号点,线段树上需要维护的就是区间内每条边的流量都大于该线段树区间内最小值的答案,理清思路后,就可以开始清理线段树上维护的标记了。
- \(min\) :表示区间内流量最小值。
- \(ma\) :表示 \(a\) 中最大值的下标。
- \(mb\) :表示 \(b\) 中最大值的下标。
- \(la\) :表示满足 \(min_{i=l}^{la-1}流量\) >区间内流量最小值的最大 \(a\) 的下表。
- \(lb\) :表示满足 \(min_{i=lb}^{r}流量\) >区间内流量最小值的最大 \(b\) 的下表。
- \(va\) :一个二元组 \((i,j)\) 满足 \(i\le j\) 为 \(a_i+b_j\) 最大值下标
- \(vb\) :一个二元组 \((i,j)\) 满足 \(i> j\) 为 \(a_i+b_j\) 最大值下标(可以不满足流量>区间内流量最小值)
- \(vc\) :一个二元组 \((i,j)\) 满足 \(i> j\) 为 \(a_i+b_j\) 最大值下标(必须要满足流量>区间内流量最小值)
还需要维护一个区间加下传的 \(tag\) 。
上传时候就是对比左右区间的最小值大小做 \(vc,la,lb\) 的转移,其余直接转移。转移的时候类似于山海经,就是标记合并。
代码:
#include<bits/stdc++.h>
using namespace std;
#define il inline
#define int long long
#define lid id<<1
#define rid id<<1|1
struct IO
{
static const int Size=(1<<21);
char buf[Size],*p1,*p2;
int st[105],Top;
~IO(){clear();}
il void clear(){fwrite(buf,1,Top,stdout);Top=0;}
il char gc(){return p1==p2&&(p2=(p1=buf)+fread(buf,1,Size,stdin),p1==p2)?EOF:*p1++;}
il void pc(const char c){Top==Size&&(clear(),0);buf[Top++]=c;}
il IO& operator >>(char& c){while(c=gc(),c==' ' || c=='\n' || c=='\r');return *this;}
template<typename T>il IO& operator >>(T& x)
{
x=0;bool f=0;char c=gc();
while(!isdigit(c)){if(c=='-') f=1;c=gc();}
while(isdigit(c)){x=(x<<1)+(x<<3)+(c^48);c=gc();}
f?x=-x:0;
return *this;
}
il IO& operator >>(string& s)
{
s="";char c=gc();
while(c==' ' || c=='\n' || c=='\r') c=gc();
while(c!=' ' && c!='\n' && c!='\r' && c!=EOF) s+=c,c=gc();
return *this;
}
il IO& operator <<(const char c){pc(c);return *this;}
template<typename T> il IO& operator <<(T x)
{
if(x<0) pc('-'),x=-x;
do st[++st[0]]=x%10,x/=10;while(x);
while(st[0]) pc(st[st[0]--]+'0');
return *this;
}
il IO& operator <<(const string s){for(auto c:s) pc(c);return *this;}
il IO& operator <<(const char* c){for(int i=0;c[i];i++) pc(c[i]);return *this;}
} fin,fout;
const int N=500010,inf=1e18;
int a[N],b[N];
struct node
{
int x,y;
};
il bool operator <(node x,node y)
{
return a[x.x]+b[x.y]<a[y.x]+b[y.y];
}
struct TreeNode
{
int tag,mn,la,lb,ma,mb;
node va,vb,vc;
} t[N<<2];
il TreeNode operator +(TreeNode x,TreeNode y)
{
TreeNode z;z.tag=0;
z.mn=min(x.mn,y.mn);
if(a[x.ma]<a[y.ma]) z.ma=x.ma;else z.ma=y.ma;
if(b[x.mb]<b[y.mb]) z.mb=x.mb;else z.mb=y.mb;
z.va=min((node){x.ma,y.mb},min(x.va,y.va));
z.vb=min(x.vb,y.vb);
z.vc=min((node){y.ma,x.mb},min(x.vc,y.vc));
if(x.mn>y.mn)
{
z.vb=min(z.vb,min(x.vc,(node){y.la,x.mb}));
z.la=(a[x.ma]<a[y.la]?x.ma:y.la);z.lb=y.lb;
}
else if(x.mn<y.mn)
{
z.vb=min(z.vb,min(y.vc,(node){y.ma,x.lb}));
z.la=x.la;z.lb=(b[x.lb]<b[y.mb]?x.lb:y.mb);
}
else
{
z.la=x.la,z.lb=y.lb;
z.vb=min(z.vb,(node){y.la,x.lb});
}
return z;
}
il void lazy_add(int id,int k)
{
t[id].tag+=k;
t[id].mn+=k;
}
il void push_down(int id)
{
if(t[id].tag)
{
lazy_add(lid,t[id].tag);
lazy_add(rid,t[id].tag);
t[id].tag=0;
}
}
il void push_up(int id)
{
t[id]=t[lid]+t[rid];
}
il void build(int id,int l,int r)
{
if(l==r)
{
t[id]={0,0,l,0,l,l,(node){l,l},(node){0,0},(node){l,l}};
return;
}
int mid=(l+r)>>1;
build(lid,l,mid);
build(rid,mid+1,r);
push_up(id);
}
il void upd(int id,int l,int r,int x)
{
if(l==r) return;
push_down(id);
int mid=(l+r)>>1;
if(x<=mid) upd(lid,l,mid,x);
else upd(rid,mid+1,r,x);
push_up(id);
}
il void add(int id,int l,int r,int L,int R,int k)
{
if(L<=l && r<=R)
{
lazy_add(id,k);
return;
}
push_down(id);
int mid=(l+r)>>1;
if(L<=mid) add(lid,l,mid,L,R,k);
if(R>mid) add(rid,mid+1,r,L,R,k);
push_up(id);
}
signed main()
{
int n,k,ans=0;fin>>n>>k;
for(int i=1;i<=n;i++) fin>>a[i];
for(int i=1;i<=n;i++) fin>>b[i];
a[0]=b[0]=inf;
build(1,0,n);
for(int i=1;i<=k;i++)
{
int p=0,x=0,y=0;
if(t[1].va<t[1].vb) x=t[1].va.x,y=t[1].va.y,p=1;
else x=t[1].vb.x,y=t[1].vb.y,p=-1;
ans+=a[x]+b[y];
a[x]=b[y]=inf;
upd(1,0,n,x);upd(1,0,n,y);
add(1,0,n,min(x,y),max(x,y)-1,p);
}
fout<<ans;
return 0;
}
CF1416F Showing Off
由于每个点都会连出一条出边,所以原图一定是一个内向的基环树森林,所以一个点一定会连向一个 \(S\) 小于等于它的点,并且指向小于它的是树边,指向等于它的是环上的边,那么无解就很好判了,只要一个数周围没有任何一个小于等于它的点,此时就无解。对于树边比较好处理,直接相连后点权取 \(S_{i,j}-S_{i',j'}\) 即可,对于环,我们发现由于四联通具有的良好性质,所具有的环一定是偶环,所以我们可以将其拆分为若干个二元环。我们可以使用黑白染色,则这个二元环中的两个点一定是一个黑点和一个白点,可以直接跑二分图匹配。当我们发现现在还会分两种点,一种是可以变为树上的点,也可以变成环上的点,还有一类是只能存在于环上的点。对于这种情况,我们可以直接把普通网络流改为上下界网络流,只能存在于环上的边把流量限制改为 \([1,1]\) ,跑有源汇上下界可行流即可。
代码:
#include<bits/stdc++.h>
using namespace std;
#define il inline
struct IO
{
static const int Size=(1<<21);
char buf[Size],*p1,*p2;
int st[105],Top;
~IO(){clear();}
il void clear(){fwrite(buf,1,Top,stdout);Top=0;}
il char gc(){return p1==p2&&(p2=(p1=buf)+fread(buf,1,Size,stdin),p1==p2)?EOF:*p1++;}
il void pc(const char c){Top==Size&&(clear(),0);buf[Top++]=c;}
il IO& operator >>(char& c){while(c=gc(),c==' ' || c=='\n' || c=='\r');return *this;}
template<typename T>il IO& operator >>(T& x)
{
x=0;bool f=0;char c=gc();
while(!isdigit(c)){if(c=='-') f=1;c=gc();}
while(isdigit(c)){x=(x<<1)+(x<<3)+(c^48);c=gc();}
f?x=-x:0;
return *this;
}
il IO& operator >>(string& s)
{
s="";char c=gc();
while(c==' ' || c=='\n' || c=='\r') c=gc();
while(c!=' ' && c!='\n' && c!='\r' && c!=EOF) s+=c,c=gc();
return *this;
}
il IO& operator <<(const char c){pc(c);return *this;}
template<typename T> il IO& operator <<(T x)
{
if(x<0) pc('-'),x=-x;
do st[++st[0]]=x%10,x/=10;while(x);
while(st[0]) pc(st[st[0]--]+'0');
return *this;
}
il IO& operator <<(const string s){for(auto c:s) pc(c);return *this;}
il IO& operator <<(const char* c){for(int i=0;c[i];i++) pc(c[i]);return *this;}
} fin,fout;
const int N=100010,inf=1e9;
int n,m,h[N],d[N],cnt,S,T,SS,TT,tot,to[N],a[N];
vector<int> vis[N],s[N],col[N];
int dx[4]={1,-1,0,0};
int dy[4]={0,0,-1,1};
char fang[4]={'D','U','L','R'};
int get_id(int x,int y)
{
return (x-1)*m+y;
}
struct Edge
{
int u,v,l,r;
} E[N<<3];
int ans[N<<3],totedge=1,cur[N],dis[N],ansf;
struct edge
{
int to,nxt,w,idx;
} e[N<<4];
il void add(int u,int v,int w,int idx)
{
totedge++;
e[totedge].idx=idx;
e[totedge].w=w;
e[totedge].nxt=h[u];
e[totedge].to=v;
h[u]=totedge;
}
il void add_edge(int u,int v,int w,int idx)
{
add(u,v,w,idx);add(v,u,0,idx);
}
il bool bfs()
{
queue<int> q;
for(int i=1;i<=tot;i++) cur[i]=h[i],dis[i]=0;
dis[SS]=1;q.push(SS);
while(!q.empty())
{
int u=q.front();q.pop();
for(int i=h[u];i;i=e[i].nxt)
{
int v=e[i].to;
if(e[i].w && !dis[v])
{
dis[v]=dis[u]+1;
q.push(v);
}
}
}
return dis[TT]>0;
}
il int dfs(int u,int val)
{
if(u==TT) return val;
int w=0;
for(int &i=cur[u];i;i=e[i].nxt)
{
int v=e[i].to;
if(e[i].w>0 && dis[v]==dis[u]+1)
{
int x=dfs(v,min(val-w,e[i].w));
if(x>0) w+=x,e[i].w-=x,e[i^1].w+=x;
if(w==val) return w;
}
}
return w;
}
il void dinic()
{
ansf=0;
while(bfs()) ansf+=dfs(SS,inf);
}
signed main()
{
int t;fin>>t;
while(t--)
{
fin>>n>>m;
for(int i=1;i<=n;i++)
{
vis[i].resize(m+1);
for(int j=0;j<=m;j++) vis[i][j]=0;
s[i].resize(m+1);
for(int j=0;j<=m;j++) s[i][j]=0;
col[i].resize(m+1);
for(int j=0;j<=m;j++) col[i][j]=0;
}
for(int i=1;i<=n;i++)
{
for(int j=(i&1)+1;j<=m;j+=2) col[i][j]=1;
}
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++) fin>>s[i][j];
}
int flag=1;
cnt=0;
tot=n*m+4;S=tot-1,T=tot,SS=tot-2,TT=tot-3;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
int fl1=0,fl2=0;
for(int f=0;f<4;f++)
{
int x=i+dx[f],y=j+dy[f];
if(x<1 || x>n || y<1 || y>m) continue;
if(s[i][j]==s[x][y]) fl2=1;
if(s[x][y]<s[i][j]) fl1=1,to[get_id(i,j)]=f,a[get_id(i,j)]=s[i][j]-s[x][y];
}
if(!fl1 && !fl2)
{
flag=0;
break;
}
if(fl2)
{
if(col[i][j]) E[++cnt]={S,get_id(i,j),fl1^1,1};
else E[++cnt]={get_id(i,j),T,fl1^1,1};
}
for(int f=0;f<4;f++)
{
int x=i+dx[f],y=j+dy[f];
if(x<1 || x>n || y<1 || y>m) continue;
if(s[i][j]==s[x][y] && col[i][j]) E[++cnt]={get_id(i,j),get_id(x,y),0,1};
}
}
if(!flag) break;
}
if(!flag)
{
fout<<"NO\n";
continue;
}
totedge=1;
for(int i=1;i<=tot;i++) d[i]=h[i]=0;
for(int i=1;i<=cnt;i++)
{
int u=E[i].u,v=E[i].v,l=E[i].l,r=E[i].r;
ans[i]=l;d[u]-=l,d[v]+=l;
add_edge(u,v,r-l,i);
}
for(int i=1;i<=tot;i++)
{
if(d[i]>0) add_edge(SS,i,d[i],cnt+1);
else if(d[i]<0) add_edge(i,TT,-d[i],cnt+1);
}
add_edge(T,S,inf,cnt+1);
dinic();
for(int i=h[SS];i;i=e[i].nxt)
{
if(e[i].w!=0)
{
flag=0;
break;
}
}
if(!flag)
{
fout<<"NO\n";
continue;
}
for(int i=3;i<=totedge;i+=2) ans[e[i].idx]+=e[i].w;
for(int i=1;i<=cnt;i++)
{
int u=E[i].u,v=E[i].v;
if(ans[i])
{
if(u==S || v==T || u==T || v==S) continue;
a[u]=s[(u-1)/m+1][(u-1)%m+1]-1;
a[v]=1;
if(m==1)
{
if(u<v) to[u]=0,to[v]=1;
else to[u]=1,to[v]=0;
}
else
{
if(u==v-m) to[u]=0,to[v]=1;
else if(u==v+m) to[u]=1,to[v]=0;
else if(u==v-1) to[u]=3,to[v]=2;
else to[u]=2,to[v]=3;
}
}
}
fout<<"YES\n";
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
fout<<a[get_id(i,j)]<<" ";
}
fout<<"\n";
}
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
fout<<fang[to[get_id(i,j)]]<<" ";
}
fout<<"\n";
}
}
return 0;
}
/*
四连通图相邻两格连出的环一定是偶环
偶环可以做拆分!!
*/
qoj#2563. Curly Racetrack
首先我们发现这个题对于选手建出来的图限制很松。只要不是自身冲突就一定可以被填满,所以我们选点时只要不自身冲突就是一个合法方案。我们又发现这个 \(L\) 型对行和列基本就是独立的,唯一的限制是要有都得有,没有都没有。
所以我们先考虑行,我们考虑一个极长的不含数字的连续段 \((l,r)\) (即 \(l\) 和 \(r\) 的位置上是数字 )。我们发现它的摆放情况和 \(r-l-1+right(l)+left(r)\) 有关,其中 \(right(l)\) 表示 \(l\) 格右边有没有出口,有就是 \(1\) ,没有就是 \(0\) ,\(left(r)\) 差不多,但表示 \(r\) 左边。我们发现当这个值为偶数时,中间的每一个不是'x'的位置都可以摆上一个 \(L\) 型通道。当这个值为奇数时,若中间有'x',那么其余不为'x'的点都可以被摆上 \(L\) 型通道;当中间全都是’o'的时候无解;当中间只有'.'的时候,中间一定要空至少一格不摆 \(L\) 型通道,我们标记这种连续段,将其称为特殊连续段。然后再对列进行相同的操作。此时确定的答案是 \(n\times m\) 减去'x'的数量再减去行上和列上特殊连续段数量,当我们发现还有些行上特殊连续段和列上特殊连续段可以通过删除一个元素来达成目的,所以我们可以跑行和列之间的匹配求出来一个最大匹配让最后的答案加上这个最大匹配就好了。
代码:
#include<bits/stdc++.h>
using namespace std;
#define il inline
struct IO
{
static const int Size=(1<<21);
char buf[Size],*p1,*p2;
int st[105],Top;
~IO(){clear();}
il void clear(){fwrite(buf,1,Top,stdout);Top=0;}
il char gc(){return p1==p2&&(p2=(p1=buf)+fread(buf,1,Size,stdin),p1==p2)?EOF:*p1++;}
il void pc(const char c){Top==Size&&(clear(),0);buf[Top++]=c;}
il IO& operator >>(char& c){while(c=gc(),c==' ' || c=='\n' || c=='\r');return *this;}
template<typename T>il IO& operator >>(T& x)
{
x=0;bool f=0;char c=gc();
while(!isdigit(c)){if(c=='-') f=1;c=gc();}
while(isdigit(c)){x=(x<<1)+(x<<3)+(c^48);c=gc();}
f?x=-x:0;
return *this;
}
il IO& operator >>(string& s)
{
s="";char c=gc();
while(c==' ' || c=='\n' || c=='\r') c=gc();
while(c!=' ' && c!='\n' && c!='\r' && c!=EOF) s+=c,c=gc();
return *this;
}
il IO& operator <<(const char c){pc(c);return *this;}
template<typename T> il IO& operator <<(T x)
{
if(x<0) pc('-'),x=-x;
do st[++st[0]]=x%10,x/=10;while(x);
while(st[0]) pc(st[st[0]--]+'0');
return *this;
}
il IO& operator <<(const string s){for(auto c:s) pc(c);return *this;}
il IO& operator <<(const char* c){for(int i=0;c[i];i++) pc(c[i]);return *this;}
} fin,fout;
const int N=110,inf=1e9;
string c[N];
int l[4]={1,1,0,0},r[4]={0,0,1,1};
int up[4]={1,0,1,0},dw[4]={0,1,0,1};
int vishang[N][N],vislie[N][N],cnt,S,T,tmp,h[N*N<<1],cur[N*N<<1],tot=1,dis[N*N<<1],ans;
struct edge
{
int to,nxt,w;
} e[N*N<<3];
void add(int u,int v,int w)
{
tot++;
e[tot].to=v;e[tot].w=w;
e[tot].nxt=h[u];h[u]=tot;
}
void add_edge(int u,int v,int w)
{
add(u,v,w);add(v,u,0);
}
bool bfs()
{
queue<int> q;
for(int i=1;i<=cnt;i++) cur[i]=h[i],dis[i]=0;
dis[S]=1,q.push(S);
while(!q.empty())
{
int u=q.front();q.pop();
for(int i=h[u];i;i=e[i].nxt)
{
int v=e[i].to;
if(e[i].w>0 && !dis[v])
{
dis[v]=dis[u]+1;
q.push(v);
}
}
}
return dis[T]>0;
}
int dfs(int u,int val)
{
if(u==T) return val;
int w=0;
for(int& i=cur[u];i;i=e[i].nxt)
{
int v=e[i].to;
if(e[i].w && dis[v]==dis[u]+1)
{
int x=dfs(v,min(e[i].w,val-w));
if(x>0) w+=x,e[i].w-=x,e[i^1].w+=x;
if(w==val) return w;
}
}
return w;
}
void dinic()
{
while(bfs()) ans+=dfs(S,inf);
}
signed main()
{
int n,m;fin>>n>>m;ans=n*m;
for(int i=1;i<=n;i++)
{
fin>>c[i];c[i]=" "+c[i];
for(int j=1;j<=m;j++) ans-=(c[i][j]=='x');
}
for(int i=1;i<=n;i++)
{
int lst=0;c[i][0]='1';c[i]+='3';
for(int j=1;j<=m+1;j++)
{
if(c[i][j]>='1' && c[i][j]<='4')
{
int val=j-lst-1+r[c[i][lst]-'1']+l[c[i][j]-'1'];
if(val&1)
{
int fl1=0,fl2=0;
for(int k=lst+1;k<j;k++)
{
if(c[i][k]=='x') fl1=1;
if(c[i][k]!='o') fl2=1;
}
if(!fl2)
{
fout<<-1;
return 0;
}
if(!fl1)
{
ans--;cnt++;
for(int k=lst+1;k<j;k++) vishang[i][k]=cnt;
}
}
lst=j;
}
}
}
tmp=cnt;
for(int i=1;i<=m+1;i++) c[0]+='1',c[n+1]+='2';
for(int j=1;j<=m;j++)
{
int lst=0;
for(int i=1;i<=n+1;i++)
{
if(c[i][j]>='1' && c[i][j]<='4')
{
int val=i-lst-1+dw[c[lst][j]-'1']+up[c[i][j]-'1'];
if(val&1)
{
int fl1=0,fl2=0;
for(int k=lst+1;k<i;k++)
{
if(c[k][j]=='x') fl1=1;
if(c[k][j]!='o') fl2=1;
}
if(!fl2)
{
fout<<-1;
return 0;
}
if(!fl1)
{
ans--;cnt++;
for(int k=lst+1;k<i;k++) vislie[k][j]=cnt;
}
}
lst=i;
}
}
}
cnt+=2,S=cnt-1,T=cnt;
for(int i=1;i<=tmp;i++) add_edge(S,i,1);
for(int i=tmp+1;i<=cnt-2;i++) add_edge(i,T,1);
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
if(c[i][j]=='.')
{
if(vishang[i][j] && vislie[i][j])
{
add_edge(vishang[i][j],vislie[i][j],1);
}
}
}
}
dinic();
fout<<ans;
return 0;
}
/*
首先发现只要不自身矛盾,那么最后一定有解。
然后发现横向和纵向是不怎么相干的。
然后就可以按列和行依次分讨。
最后在合在一起跑匹配
*/
浙公网安备 33010602011771号