图论专题 1
喜报:图论专题 1 前五题都是 uoj 题。后边是 WC2007 剪刀石头布和两个 *3100。
喜报:我不会图论!
观察了一下发现除了我们以外做这些题的确实都不是活人。那我们在和波特对战!很恐怖!
随手交了一发新年的追逐战居然最优解了。该说 uoj 卡多项式全家桶最优解的没多少吗。
速度明显慢下来了,一套题切了两天。
UNR #5获奖名单
通过这是图论专题的题猜测一下是给的两个点之间连边,然后只有一个字符的新建一个超级源点连边。那么最后一条欧拉回路就是答案回文串中两段对应的段。
然后小小分讨一下。如果回文串长偶数,那么一定是若干如上欧拉回路和若干出现偶数次的边配对,然后可能在最中间有一个出现奇数次的自环。如果是奇数,那么手模第二个样例可以得到长奇数的回文串就是一条从超级源点出发的欧拉路径,别的同样是出现偶数次的配对。那分讨即可。
细节好多,真就写挂一点就炸。
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <iostream>
#include <queue>
#include <map>
#include <vector>
using namespace std;
int n,m,len,u[500010],v[500010];
struct node{
int v,id,od,next;
}edge[1000010];
int t,head[500010];
void add(int u,int v,int id,int od){
edge[++t].v=v;edge[t].id=id;edge[t].od=od;edge[t].next=head[u];head[u]=t;
}
bool vis[500010];
int cnt,ans[500010],ans2[500010],pos[500010];
void dfs(int x){
for(int &i=head[x];i;i=edge[i].next){
int id=edge[i].id;
if(vis[edge[i].id])continue;
vis[edge[i].id]=true;
ans2[edge[i].id]^=edge[i].od;
dfs(edge[i].v);
pos[++cnt]=id;
}
}
int fa[500010];
int find(int x){
return x==fa[x]?fa[x]:fa[x]=find(fa[x]);
}
void merge(int x,int y){
fa[find(y)]=find(x);
}
map<pair<int,int>,int>mp;
vector<int>g[500010];
void solve1(){
int l=1,r=n;
for(int i=1;i<=n;i++){
if(find(u[i])==find(0))continue;
int x=mp[make_pair(u[i],v[i])];
if(g[x].empty())continue;
if(!(g[x].size()&1)){
while(!g[x].empty()){
int a=g[x].back();g[x].pop_back();
int b=g[x].back();g[x].pop_back();
ans[l++]=a;ans[r--]=b;ans2[b]^=1;
}
}
}
for(int i=cnt;i>=1;){
ans[l++]=pos[i];i--;
if(i)ans[r--]=pos[i];ans2[pos[i]]^=1;i--;
}
}
void solve2(){
int l=1,r=n;
for(int i=cnt;i>=1;){
ans[l++]=pos[i];i--;
ans[r--]=pos[i];ans2[pos[i]]^=1;i--;
}
if(cnt&1)r++;
for(int i=1;i<=n;i++){
if(find(u[i])==find(0))continue;
int x=mp[make_pair(u[i],v[i])];
if(g[x].empty())continue;
if(!(g[x].size()&1)){
while(!g[x].empty()){
int a=g[x].back();g[x].pop_back();
int b=g[x].back();g[x].pop_back();
ans[l++]=a;ans[r--]=b;ans2[b]^=1;
}
}
}
for(int i=1;i<=n;i++){
if(find(u[i])==find(0))continue;
int x=mp[make_pair(u[i],v[i])];
if(g[x].empty())continue;
while(g[x].size()>1){
int a=g[x].back();g[x].pop_back();
int b=g[x].back();g[x].pop_back();
ans[l++]=a;ans[r--]=b;ans2[b]^=1;
}
int a=g[x].back();ans[l++]=a;
return;
}
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++)fa[i]=i;
for(int i=1;i<=n;i++){
int num;scanf("%d",&num);len+=num;
if(num==1)scanf("%d",&u[i]);
else scanf("%d%d",&u[i],&v[i]);
merge(u[i],v[i]);
if(u[i]>v[i])swap(u[i],v[i]),ans2[i]^=1;
add(u[i],v[i],i,0);add(v[i],u[i],i,1);
if(mp.find(make_pair(u[i],v[i]))==mp.end())mp[make_pair(u[i],v[i])]=++cnt;
g[mp[make_pair(u[i],v[i])]].push_back(i);
}
cnt=0;
dfs(0);
if(len&1)solve1();
else solve2();
for(int i=1;i<=n;i++)printf("%d ",ans[i]);puts("");
for(int i=1;i<=n;i++)printf("%d ",ans2[ans[i]]);puts("");
return 0;
}
UER #9 知识网络
首先显然的可以给每个标签开个虚点连到所有标签内的点。然后发现现在瓶颈在 \(O(n)\) 次最短路,考虑怎么不跑这么多最短路。观察到 \(k\) 较小,考虑对 \(k\) 处理。
对每个标签为起点建立最短路 DAG,然后一次处理一个标签内所有点的答案。分析标签内的点到其他点的最短路,如果经过标签,那么就是标签到达目标点的最短路。如果不经过,就是它 \(-1\)。不经过的充要条件是目标点在当前点的最短路 DAG 上的后继上,可以 bitset 维护。
长度不固定,需要手写 bitset。题解的做法是每 \(64\) 个点一起处理,用 unsigned long long 压起来。
01 最短路写挂 T 了两发,我是 nt。
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <vector>
#include <deque>
#include <queue>
using namespace std;
int n,m,k;
long long ans[310];
struct node{
int v,w,next;
}edge[200010];
int t,head[100010];
void add(int u,int v,int w){
edge[++t].v=v;edge[t].w=w;edge[t].next=head[u];head[u]=t;
}
int dis[100010],ind[100010],d[100010];
vector<int>g[100010],belong[100010];
unsigned long long f[100010];
void tuopu(int st){
queue<int>q;q.push(st);
for(int i=1;i<=n+k;i++)d[i]=ind[i];
while(!q.empty()){
int x=q.front();q.pop();
for(int v:g[x]){
f[v]|=f[x];d[v]--;
if(!d[v])q.push(v);
}
}
}
void solve(int st){
deque<int>q;
for(int i=1;i<=n+k;i++)dis[i]=0x3f3f3f3f,ind[i]=0,g[i].clear();
q.push_back(n+st);dis[n+st]=0;
while(!q.empty()){
int x=q.front();q.pop_front();
for(int i=head[x];i;i=edge[i].next){
if(dis[edge[i].v]>dis[x]+edge[i].w){
dis[edge[i].v]=dis[x]+edge[i].w;
if(edge[i].w)q.push_back(edge[i].v);
else q.push_front(edge[i].v);
}
}
}
for(int x=1;x<=n+k;x++){
for(int i=head[x];i;i=edge[i].next){
if(dis[edge[i].v]==dis[x]+edge[i].w){
g[x].push_back(edge[i].v);
ind[edge[i].v]++;
}
}
}
for(int i=0;i<belong[st].size();i+=64){
int l=i,r=min(i+64,(int)belong[st].size())-1;
for(int j=1;j<=n+k;j++)f[j]=0;
for(int j=l;j<=r;j++)f[belong[st][j]]|=1ull<<j-i;
tuopu(n+st);
for(int j=1;j<=n;j++){
if(dis[j]!=0x3f3f3f3f){
ans[dis[j]]+=__builtin_popcountll(f[j]);
ans[dis[j]+1]+=r-l+1-__builtin_popcountll(f[j]);
}
else ans[k<<1|1]+=r-l+1;
}
}
}
int main(){
scanf("%d%d%d",&n,&m,&k);
for(int i=1;i<=n;i++){
int x;scanf("%d",&x);belong[x].push_back(i);
add(i,n+x,0);add(n+x,i,1);
}
for(int i=1;i<=m;i++){
int u,v;scanf("%d%d",&u,&v);
add(u,v,1);add(v,u,1);
}
for(int i=1;i<=k;i++)solve(i);
ans[1]=0;
for(int i=1;i<=(k<<1|1);i++)printf("%lld ",ans[i]>>1);puts("");
return 0;
}
UR #2 跳蚤公路
坐大牢!
首先你要做的是找到所有环,然后它们的边权都形如 \(kx+b\),对它们非负的范围求交就是答案。
考虑怎么找负环。先看如果 \(x\) 已知怎么找负环。Floyd 太慢了而且没啥好性质,最多好像可以做到 \(O(n^5)\),过不去。SPFA 能判负环,但是得到的点不一定在负环上就似了。考虑 Bellman-Ford。
设 \(f_{t,v}\) 为走 \(t\) 步到 \(v\) 的最短路,那么对于负环一定存在 \(f_{n,v}<f_{n-1,v}\)。那么现在对于所有的 \(x\) 求,就设 \(f_{t,v,k}\) 为走 \(t\) 步到达 \(v\),路径上 \(x\) 的系数为 \(k\) 的最短路,那么跑完 Bellman-Ford 之后在每个点将所有 \(f_{n-1}\) 和 \(f_n\) 对比,需要解不等式:
怎么解?首先枚举所有 \(k\),取它们的交。然后此时确定了 \(k\),枚举所有 \(j\),取它们的并,就消掉了 \(\min\)。
最后统计 \(v\) 的答案,需要考虑所有与 \(v\) 连通的环。之前处理了 \(j\) 的并集,只需要求得 \(k\) 的交集即可。一点小分讨。
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <iostream>
#include <vector>
#define int long long
using namespace std;
const int inf=0x3f3f3f3f3f3f3f3f;
int n,m,g[110][110];
struct node{
int u,v,w,k;
}edge[10010];
int f[110][110][220];
vector<pair<int,int> >ans[110];
signed main(){
scanf("%lld%lld",&n,&m);
for(int i=1;i<=m;i++){
int u,v,w,k;scanf("%lld%lld%lld%lld",&u,&v,&w,&k);
edge[i]={u,v,w,k};g[u][v]=1;
}
for(int i=1;i<=n;i++)g[i][i]=1;
for(int k=1;k<=n;k++){
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
g[i][j]|=g[i][k]&g[k][j];
}
}
}
memset(f,0x3f,sizeof(f));
f[0][1][105]=0;
for(int i=1;i<=n;i++){
memcpy(f[i],f[i-1],sizeof(f[i]));
for(int j=1;j<=m;j++){
int u=edge[j].u,v=edge[j].v;
for(int k=105-n;k<=105+n;k++){
if(f[i-1][u][k]<inf)f[i][v][k+edge[j].k]=min(f[i][v][k+edge[j].k],f[i-1][u][k]+edge[j].w);
}
}
}
for(int i=1;i<=n;i++){
for(int k=105-n;k<=105+n;k++){
if(f[n][i][k]!=inf){
int l=-inf,r=inf;bool jud=true;
for(int j=105-n;j<=105+n;j++){
if(f[n-1][i][j]!=inf){
if(k>j)r=min(r,(long long)ceil(1.0*(f[n-1][i][j]-f[n][i][k])/(k-j)));
else if(k<j) l=max(l,(long long)floor(1.0*(f[n-1][i][j]-f[n][i][k])/(k-j)));
else if(f[n][i][k]>=f[n-1][i][j]){
jud=false;break;
}
}
}
if(jud&&l<r)ans[i].push_back(make_pair(l,r));
}
}
}
for(int i=1;i<=n;i++){
vector<pair<int,int> >s;
for(int j=1;j<=n;j++){
if(g[1][j]&&g[j][i]){
for(pair<int,int>p:ans[j])s.push_back(p);
}
}
sort(s.begin(),s.end());
int l=inf,r=-inf,pre=-inf;bool jud=false;
for(int j=0;j<s.size();j++){
if(!j&&s[j].first>-inf){
l=-inf;r=s[j].first;jud=true;break;
}
if(pre!=-inf&&pre<=s[j].first){
l=pre;r=s[j].first;jud=true;break;
}
pre=max(pre,s[j].second);
}
if(!jud&&pre<inf)l=pre,r=inf;
if(l==-inf||r==inf||s.empty())puts("-1");
else printf("%lld\n",max(0ll,r-l+1));
}
return 0;
}
UNR #4 同构判定鸭
咋一个比一个逆天。不过吃片感康恶心一上午中午没怎么吃饭胃疼一下午更逆天。现在头晕的要命,想睡觉。
这题好不可做啊!交个简单的代码跑路吧:
print 'Same'
期望得分:100。您切了!
好了回到本题。先考虑 DAG 的部分分,显然最长坏串不会超过 \(\max(n_1,n_2)\)。那么给不同位置的相同字符不同的哈希值,令字符串哈希函数为所有字符哈希乘积,集合的哈希函数为所有字符串加和,这样容易每次给集合内所有串加一个字符。设 \(f_{x,i}\) 为 \(x\) 开头的所有长 \(i\) 字符串哈希值和,那么容易在 \(O(nm)\) 复杂度内算出所有 \(f\) 然后按位贪心判断即可。
然后是一般的情况。猜测既然他让我们输出方案那答案一定不会太长。事实上有结论:最短坏串最长 \(n_1+n_2\)。那么仍然按上述做法做即可。证明比较复杂不过还是放一下,在代码后边。
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <iostream>
#include <vector>
#include <ctime>
#include <random>
using namespace std;
const unsigned long long mod=1000000007;
unsigned long long num[1010][26];
mt19937_64 rnd(time(0)^(unsigned long long)(new char));
struct graph{
int n,m;
vector<pair<int,char> >g[510];
unsigned long long hs[510][1010],f[510];
void init(){
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++){
int u,v;char s[5];scanf("%d%d%s",&u,&v,s);
g[u].push_back(make_pair(v,s[0]));
}
for(int i=1;i<=n;i++)hs[i][0]=f[i]=1;
}
unsigned long long check(int len){
unsigned long long ans=0;
for(int x=1;x<=n;x++){
for(pair<int,char>p:g[x])hs[x][len]=(hs[x][len]+hs[p.first][len-1]*num[len][p.second-'a'])%mod;
ans=(ans+hs[x][len])%mod;
}
return ans;
}
unsigned long long get(int len,char ch){
unsigned long long ans=0;
for(int x=1;x<=n;x++){
for(pair<int,char>p:g[x])if(p.second==ch)ans=(ans+f[x]*hs[p.first][len-1]%mod*num[len][ch-'a'])%mod;
}
return ans;
}
void update(int len,char ch){
static unsigned long long tmp[510];
for(int x=1;x<=n;x++){
for(pair<int,char>p:g[x])if(p.second==ch)tmp[p.first]=(tmp[p.first]+f[x]*num[len][ch-'a'])%mod;
}
for(int i=1;i<=n;i++)f[i]=tmp[i],tmp[i]=0;
}
}g1,g2;
void print(int len){
for(int i=len;i>=1;i--){
for(int j=0;j<26;j++){
if(g1.get(i,j+'a')!=g2.get(i,j+'a')){
putchar(j+'a');
g1.update(i,j+'a');g2.update(i,j+'a');
break;
}
}
}
puts("");
}
int main(){
g1.init();g2.init();
for(int i=1;i<=g1.n+g2.n;i++){
for(int j=0;j<26;j++)num[i][j]=rnd()%mod;
}
for(int i=1;i<=g1.n+g2.n;i++){
if(g1.check(i)!=g2.check(i)){
print(i);return 0;
}
}
puts("Same");
return 0;
}
现在证明最短坏串长度不超过 \(n_1+n_2\) 的结论。
首先我们将两个图拼成一个 \(n_1+n_2\) 个点的图,前 \(n_1\) 个属于第一张,后 \(n_2\) 个属于第二章。如果匹配串 \(s\),那么设 \(f_{i,x}\) 为结尾 \(x\),匹配长度 \(i\) 的路径数,显然 \(f\) 的递推式是线性的,于是可以写成矩阵。转移只和当前字符和节点有关,那么得到 \(26\) 个转移矩阵 \(M\)。把 \(f_0\) 写成一个行向量 \(v^T\)(初始全 \(1\)),那么 \(f_{L}=v^T\prod_{i=1}^{len}M_{s_i}\)。同样的,设向量 \(u\) 前 \(n_1\) 个是 \(1\),后 \(n_2\) 个是 \(-1\),那么好串当且仅当上边那个向量右乘上 \(u\) 的值是 \(0\)。
另设 \(V_k\) 为所有长度不超过 \(k\) 的串的 \(f_L\) 集合,若两张图不等价,则存在 \(k\) 满足 \(V_k\) 内有元素和它不正交,即乘积不是 \(0\)。
考虑每个 \(V_k\) 的张成空间 \(U_k\),它们显然互相包含且可以递推。于是它们维数单调不降。又因为最多 \(n_1+n_2\) 个点,因此维数最高 \(n_1+n_2\),即最短坏串最长 \(n_1+n_2\)。
uoj #461 新年的Dog划分
不难,但是有点细节没想明白。
首先我们显然可以 \(O(n^2)\) 确认原图的一颗生成树然后跑染色。然后用二分搞一下就是 \(O(n\log n)\) 的。具体的,假设目前确定了 \(P\) 集合的点,那么二分剩下的点组成的 \(Q\) 集合在保留哪个前缀的边时联通,即可确认一条边,然后把这个点扔到 \(P\) 集合。
然后考虑怎么判得到的点在哪边。实际上可以把已经确认的点按在哪一边分类,删掉一边和这个点连通的所有边判剩下的是否连通就行了,如果都连通则显然不是二分图。
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <iostream>
#include <queue>
#include <map>
#include <vector>
#include "graph.h"
using namespace std;
vector<int>P,Q;
vector<pair<int,int> >edge;
int belong[210];
bool check(int mid){
edge.clear();
for(int v:Q){
if(edge.size()==mid*P.size())break;
for(int x:P)edge.push_back(make_pair(x,v));
}
return query(edge);
}
int solve(){
int l=1,r=Q.size();
while(l<r){
int mid=(l+r)>>1;
if(check(mid))l=mid+1;
else r=mid;
}
edge.clear();
for(int v:Q){
for(int x:P)edge.push_back(make_pair(x,v));
if(edge.size()==l*P.size())return v;
}
return 114514;
}
vector<int> check_bipartite(int n){
P.push_back(0);
for(int i=1;i<n;i++)Q.push_back(i);
while(!Q.empty()){
int v=solve();
vector<pair<int,int> >ret;
for(pair<int,int>p:edge){
if(belong[p.first]||p.second!=v)ret.push_back(p);
}
bool ans1=query(ret);ret.clear();
for(pair<int,int>p:edge){
if(!belong[p.first]||p.second!=v)ret.push_back(p);
}
bool ans2=query(ret);ret.clear();
if(ans1&&ans2){
vector<int>ans;return ans;
}
if(ans1)belong[v]=1;
else belong[v]=0;
for(vector<int>::iterator it=Q.begin();it!=Q.end();++it){
if(*it==v){
Q.erase(it);break;
}
}
P.push_back(v);
}
vector<int>ans;
for(int i=0;i<n;i++)if(belong[i])ans.push_back(i);
return ans;
}
CF1264E Beautiful League
CF 重题 WC。蚌埠。双倍经验 WC2007 剪刀石头布。
对于每组关系胜者向败者连边,那么手模可以得到设点 \(x\) 入度为 \(d\),那么减少的三元环个数为 \(\dbinom d2\)。差分一个点入度增加 \(1\) 破坏的三元环个数(\(0,1,2,\cdots,n-1\))跑费用流即可。具体的,每条边变成一个点,源点向边的点连流量 \(1\),费用 \(0\) 的边,每个点向汇点连流量 \(1\),费用 \(0,1,2,\cdots,n-1\) 的边,然后对于每条边,确定关系的向败者连流量 \(1\),费用 \(0\) 的边,没确定的向两个点连,然后跑最小费用最大流。
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <iostream>
#include <queue>
using namespace std;
struct node{
int v,w,val,next;
}edge[120010];
int n,m,S,T,cost,ANS,tot=1,head[6010],dis[6010];
bool v[6010];
void Add(int u,int v,int w,int val){
edge[++tot].v=v;edge[tot].w=w;edge[tot].next=head[u];head[u]=tot;
edge[tot].val=val;
}
void add(int u,int v,int w,int val){
Add(u,v,w,val);Add(v,u,0,-val);
}
queue<int>q;
bool spfa(int st){
for(int i=0;i<=T;i++)dis[i]=0x3f3f3f3f;
q.push(st);dis[st]=0;
while(!q.empty()){
int x=q.front();q.pop();v[x]=false;
for(int i=head[x];i;i=edge[i].next){
if(edge[i].w&&dis[edge[i].v]>dis[x]+edge[i].val){
dis[edge[i].v]=dis[x]+edge[i].val;
if(!v[edge[i].v])q.push(edge[i].v),v[edge[i].v]=true;
}
}
}
return dis[T]!=0x3f3f3f3f;
}
int dfs(int x,int flow){
if(x==T)return flow;
int sum=0;
v[x]=true;
for(int i=head[x];i;i=edge[i].next){
if(!v[edge[i].v]&&edge[i].w&&dis[edge[i].v]==dis[x]+edge[i].val){
int ret=dfs(edge[i].v,min(flow,edge[i].w));
if(ret){
edge[i].w-=ret;edge[i^1].w+=ret;
flow-=ret;sum+=ret;cost+=ret*edge[i].val;
if(!flow)break;
}
else dis[edge[i].v]=-1;
}
}
v[x]=false;
return sum;
}
int pos[6010],id[110][110],ans[110][110];
int getans(int x){
for(int i=head[x];i;i=edge[i].next){
if(edge[i].v&&edge[i].w==0)return edge[i].v;
}
return 114514;
}
int main(){
scanf("%d%d",&n,&m);
int cnt=n;
for(int i=1;i<=n;i++)for(int j=1;j<i;j++)id[i][j]=++cnt;
S=0,T=++cnt;
for(int i=1;i<=n;i++){
for(int j=0;j<n;j++)add(i,T,1,j);
}
for(int i=1;i<=m;i++){
int u,v;scanf("%d%d",&u,&v);
ans[u][v]=1;ans[v][u]=2;
}
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
int x=ans[i][j];ans[i][j]=0;
if(i<=j)continue;
add(S,id[i][j],1,0);
if(x==2)add(id[i][j],i,1,0);
else if(x==1)add(id[i][j],j,1,0);
else add(id[i][j],i,1,0),add(id[i][j],j,1,0);
}
}
while(spfa(S))ANS+=dfs(S,0x3f3f3f3f);
for(int i=1;i<=n;i++){
for(int j=1;j<i;j++){
if(getans(id[i][j])==i)ans[i][j]=0,ans[j][i]=1;
else ans[i][j]=1,ans[j][i]=0;
}
}
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
printf("%d",ans[i][j]);
}
puts("");
}
return 0;
}
CF1239E Turtle
真的是图论吗。
首先有性质第一行一定单调不降,第二行不增。然后由此得到一定在第一列或者最后一列挪到第二行。第一个和最后一个格子必须经过,放最小的,于是变成剩下 \(2n-2\) 个选 \(n-1\) 个,要得到最大的不超过 \(\dfrac{\sum a}2\) 的数的方案。背包即可。
讨厌输出方案。
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <iostream>
#include <queue>
using namespace std;
int n,m,sum,a[60],pre[60][1200010];
bool dp[60][1200010],vis[60];
void print(int n,int x){
if(!n)return;
int val=a[pre[n][x]];
print(n-1,x-val);
printf("%d ",val);
vis[pre[n][x]]=true;
}
int main(){
scanf("%d",&n);
for(int i=1;i<=(n<<1);i++){
scanf("%d",&a[i]);
m=max(m,a[i]),sum+=a[i];
}
sort(a+1,a+2*n+1);sum=(sum-a[1]-a[2])>>1;
dp[0][0]=true;n--;
for(int i=3;i<=2*n+2;i++){
for(int j=min(i,n);j>=1;j--){
for(int k=sum;k>=a[i];k--){
if(dp[j][k])continue;
if(dp[j-1][k-a[i]])pre[j][k]=i,dp[j][k]=true;
}
}
}
for(int i=sum;i>=0;i--)if(dp[n][i]){
printf("%d ",a[1]);print(n,i);puts("");vis[1]=true;
for(int i=2*n+2;i>=1;i--)if(!vis[i])printf("%d ",a[i]);puts("");
return 0;
}
}
CF521E Cycling City
疑似唯一可做题。不过感觉这个题和上边一个都没有 *3100。
首先 dfs 树,然后如果有树边被非树边覆盖两次则直接出解。直接暴力复杂度是对的,因为每条边最多覆盖两次。然而输出方案,懒得写了,对着题解贺。
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <iostream>
#include <vector>
using namespace std;
int n,m;
struct node{
int v,next;
}edge[400010];
int t,head[200010];
void add(int u,int v){
edge[++t].v=v;edge[t].next=head[u];head[u]=t;
}
int dep[200010],fa[200010];
bool vis[200010],v[200010];
pair<int,int>p[200010];
int lca(int x,int y){
if(dep[x]>dep[y])swap(x,y);
while(dep[y]>dep[x])y=fa[y];
while(x!=y)x=fa[x],y=fa[y];
return x;
}
int stk[200010];
void ins(int x,int y){
while(x!=fa[y])stk[++stk[0]]=x,x=fa[x];
}
void getans(int u,int x,int y){
puts("YES");
int a=p[u].first,b=p[u].second;
if(dep[b]>dep[y])swap(a,x),swap(b,y);
int lc=lca(a,x);
stk[0]=0;
ins(lc,y);
reverse(stk+1,stk+stk[0]+1);
for(int i=0;i<=stk[0];i++)printf("%d ",stk[i]);puts("");
stk[0]=0;
ins(y,b);ins(a,lc);
for(int i=0;i<=stk[0];i++)printf("%d ",stk[i]);puts("");
stk[0]=0;
ins(y,y);ins(x,lc);
for(int i=0;i<=stk[0];i++)printf("%d ",stk[i]);puts("");
exit(0);
}
void dfs(int x,int f){
dep[x]=dep[f]+1;fa[x]=f;vis[x]=v[x]=true;
for(int i=head[x];i;i=edge[i].next){
if(edge[i].v==f)continue;
if(!vis[edge[i].v])dfs(edge[i].v,x);
else if(v[edge[i].v]){
int u=x;
while(u!=edge[i].v){
if(p[u]!=make_pair(0,0))getans(u,x,edge[i].v);
else p[u]=make_pair(x,edge[i].v);
u=fa[u];
}
}
}
v[x]=false;
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++){
int u,v;scanf("%d%d",&u,&v);
add(u,v);add(v,u);
}
for(int i=1;i<=n;i++)if(!vis[i])dfs(i,0);
puts("NO");
return 0;
}