首页 > 其他分享 >图论专题 1

图论专题 1

时间:2023-05-21 18:56:17浏览次数:34  
标签:图论 专题 return int long edge ans include

喜报:图论专题 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\) 对比,需要解不等式:

\[\min\{kx+f_{n,v,k}\}\ge\min\{jx+f_{n-1,v,j}\} \]

怎么解?首先枚举所有 \(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;
}

标签:图论,专题,return,int,long,edge,ans,include
From: https://www.cnblogs.com/gtm1514/p/17418976.html

相关文章

  • 【BSP视频教程】BSP视频教程第26期:CAN/CANFD/CANopen专题,CANFD整个运行机制精讲,图文并
     上期视频教程为大家分享了很多CAN理论方面的知识,本期视频教程我们在实战应用中学习CANFD。CANFD涉及到的知识点非常多,我们本期重点是把CANFD整个运行机制搞明白,知其然知其所以然。视频:https://www.bilibili.com/video/BV1iX4y117Bv视频提纲:参考资料:1、【原创】H7-TOOL的CANFDT......
  • 【BSP视频教程】BSP视频教程第26期:CAN/CANFD/CANopen专题,CANFD整个运行机制精讲,图文并
     上期视频教程为大家分享了很多CAN理论方面的知识,本期视频教程我们在实战应用中学习CANFD。CANFD涉及到的知识点非常多,我们本期重点是把CANFD整个运行机制搞明白,知其然知其所以然。视频:https://www.bilibili.com/video/BV1iX4y117Bv视频提纲:参考资料:1、【原创】H7-TOOL的CANFDT......
  • 字符串专题1
    A.CF547EMikeandFriends多校考过,当时拿根号过的建立\(AC\)自动机,询问转成差分形式,每次把一个字符串的所有前缀位置都\(+1\),询问某个点子树内总和树状数组即可用广义\(SAM\)+线段树合并也可以无脑过B.MishaandLCPonTree匹配两个串好像除了\(Hash\)也没啥太......
  • 图论 plus
    P5304「GXOI/GZOI2019」旅行者如果是无向边,那么以所有感兴趣的城市为起点跑一遍Dijkstra即可,遇到访问过的点就说明找到了最短路。但题目已经写清楚可能存在自环和重边,而起点和终点相同不算两两之间,所以还需要额外记录一下起点。变成有向边后,我们不能在任意一个点处合并最短......
  • 【BSP视频教程】BSP视频教程第26期:CAN/CANFD/CANopen专题,CANFD整个运行机制精讲,图文并
    视频教程汇总帖:https://www.armbbs.cn/forum.php?mod=viewthread&tid=110519 上期视频教程为大家分享了很多CAN理论方面的知识,本期视频教程我们在实战应用中学习CANFD。CANFD涉及到的知识点非常多,我们本期重点是把CANFD整个运行机制搞明白,知其然知其所以然。视频:https:/......
  • 中二羊专题:栋栋的入门题(前缀和)
    原题标题虽然是栋栋的入门题,但它并不是入门题。原题的题目描述是:给出N个整数,以及M个求和范围,求出每一个范围的数字的和。提示:显然,这并不是一道入门题。这就要用到一种新的思想:前缀思想。进入正题数组\(a\)(此处方便讲解,忽略下标\(0\))有这\(5\)个数字:\(1,3,2,1,5\)......
  • 中二羊专题:栋栋吃糖果
    U163898题目题目背景栋栋参加比赛拿下了一等奖,老师奖励了很多糖果。题目描述一共有\(m\)种糖果,其中第i种糖果的数量为\(m_i\)。栋栋吃糖时会获得快乐值,并且他喜欢换着口味吃糖。当栋栋吃下第一个糖果时快乐值为\(0\),接下来,每吃一个不同口味的糖果(与上一个糖不同),快乐......
  • 【专题】中国企业财务数字化转型白皮书报告PDF合集分享(附原数据表)
    报告链接:http://tecdat.cn/?p=32389原文出处:拓端数据部落公众号新冠疫情等对商业活动进行了重新塑造,并使金融活动在商业活动中的位置发生了变化。在可持续发展的时代背景下,财务人员需要适应新的工作模式,主动接受新的技术,将关注的重点从传统的财务报告范围拓展到可持续性、包容性......
  • 敏捷专题:下一代的飞机交付
    ​随着信息化和网络化的发展,航空航天领域的装备已经发展成为软件密集型系统,软件负责完成航空装备的大部分功能。资料显示,以美国的F-22战斗机为例,由软件实现的功能已经达到80%以上,航空航天领域的软件规模和重要度与日俱增。▲航空航天领域的软件特点 上述特点其实也是航空航天......
  • 学习笔记:矩阵快速幂与图论
    1计算路径数对于一个边权为\(\bf1\)的图(有向或无向)的邻接矩阵\(G\),考虑它的幂的意义是什么。设\(c_{i,j}\)表示\(i\)和\(j\)之间是否有连边,则有\[G=\begin{bmatrix}c_{1,1}&c_{1,2}&\cdots&c_{1,n}\\c_{2,1}&c_{2,2}&\cdots&c_{2,n}\\\vdots&am......