acm竞赛题库与解析

第一章 数学

1.1概率

Coupons赛区/题库:UVa 10288

在这里插入图片描述

【算法分析】假设当前已经有k种Coupons的概率是(n-k)/n,所以需要步数的期望是n/(n-k).。求和得到步数的期望是n/n+n/(n-1)+…+n/1

import java.io.BufferedInputStream;
import java.util.Scanner;
public class ACM {
    
    
        Scanner cin = new Scanner(new BufferedInputStream(System.in));
        long gcd(long a, long b) {
    
    
            return b == 0 ? a : gcd(b, a%b);
        }
        long lcm(long a, long b) {
    
    
            return a / gcd(a,b) * b;
        }
        int len(long v) {
    
    
            int res = 0;
            do {
    
     v /= 10;  res++; } while(v > 0);
            return res;
        }
        void printCh(char c, int x) {
    
    
            while(x --> 0) System.out.print(c);
        }
        //输出分数
        void output(long a, long b, long c) {
    
    
            if(b == 0) {
    
    
                System.out.println(a);
                return;
            }
            printCh(' ', len(a)+1);
            System.out.println(b);
            System.out.print(a + " ");
            printCh('-', len(c));
            System.out.println();
            printCh(' ', len(a)+1);
            System.out.println(c);
        }
        void MAIN() {
    
    
            while(cin.hasNext()) {
    
    
                int n = cin.nextInt();
                long b = 0, c = 1;
                for(int i = 2; i <= n; i++) c = lcm(c, i);
                for(int i = 0; i < n; i++) b += c / (n-i) * n;
                output(b/c, b%c/gcd(b%c,c), c/gcd(b%c,c));
            }
        }
        public static void main(String args[]) {
    
    
            new ACM().MAIN();
        }
}

Generator 赛区/题库 HangZhou 2005

在这里插入图片描述
在这里插入图片描述

解题思路:首先要对S进行预处理,求出失配数组。
定义dp[i]表示末尾部分匹配了i个S串所需要的次数期望,每次枚举可能出现的字符1~n。对于S字符串,i+1肯定是确定的字符,所以对于其他字符肯定是不匹配的。
假设现在生成了k字符,并且说k字符不等于S[i+1],那么根据S的失配数组,我们可以确定目前还匹配几个字符,(类似KMP匹配问题),假设有匹配j个字符,那么也就是说从匹配j个到匹配i个我们还要重新生成dp[i] - dp[j]次(期望)。
于是f(i)(从匹配i-1到匹配i个需要生成次数的期望)即有公式f(i)=1+∑i=1n(dp[i−1]−dp[lose(k)]) / n+n−1nf(i)(lose(k)为对应生成字符为k的情况下还匹配的字符数)
dp[i] = dp[i-1] + f(i)

#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const int N = 50;

int n, m, k, t, ans, kcase, cases;
int a[N];
int nex[N];
char s[N];
ll f[N];
int len;

void get_nex(char* s)
{
    
    
    for (int i = 2, j = 0; i <= len; ++ i) {
    
    
        while(j != 0 && s[j + 1] != s[i])
            j = nex[j];
        if(s[j + 1] == s[i])
            ++ j;
        nex[i] = j;
    }
}
void solve()
{
    
    
    scanf("%d%s", &n, s + 1);
    len = strlen(s + 1);
    get_nex(s);
    f[0] = 0;
    for (int i =0; i <= len - 1; ++ i) {
    
    
        f[i + 1] = (f[i] + 1) * n;
        for (int j = 0; j < n; ++ j) {
    
    
            if(s[i + 1] == 'A' + j)
                continue;
            int pos = i;
            while(pos && s[pos + 1] != j + 'A')
                pos = nex[pos];
            if(s[pos + 1] == j + 'A')
                ++ pos;
            f[i + 1] -= f[pos];
        }
    }
    printf("%lld\n", f[len]);
}

int main()
{
    
    
    scanf("%d", &t);
    while(t -- ) {
    
    
        printf("Case %d:\n", ++ kcase);
        solve();
        if(t)
            puts("");
    }
    return 0;
}

第二章数据结构

2.1优先队列

The Lazy Progrmmer 赛区/题库 NEERC 2004

在这里插入图片描述

【算法分析】
首先完成合同的顺序一定要正确的,即按di 排序。所以首先吧合同按di从小到排序。
接下来我们这么思考,按照编号依次排序完成合同,若发现第i个合同无法在截至日期前完成,便从之前完成的任务中选取一个ai最大的任务,付钱使得这个任务尽快完成。
可以从第一个到第n个合同同时扫描,对于第i个合同,将一个二元组(ai,xi)添加到以ai为关键字的大根堆中,xi表示已经为这个任务付了多少钱。初始时xi=0
同时维护一个当前任务的总时间T,如果T>di,那么不断取出堆顶元素(aj,xj),并将T减去aj乘以一个钱数直到T=di,并把答案加上对应花费的钱数,然后一个二元组变成了(ai,bi/ai),表示这个任务完成的时间为零,问你已经无法通过付更多的钱使得此任务完成的更快。因此将其从任务中删掉

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <queue>

using namespace std;
int n;
struct node{
    
    
    int a,b,d;
    bool operator<(const node &x)
    const{
    
    
        return a<x.a;
    }
}c[100005];
bool cmp(node x,node y){
    
    
    return x.d<y.d;
}
int main(){
    
    
    ios::sync_with_stdio(false);//提示读取效率
    scanf("%d",&n);
    priority_queue<node> q;
    for(int i=0;i<n;i++)
        scanf("%d%d%d",&c[i].a,&c[i].b,&c[i].d);
    sort(c,c+n,cmp);
    double res=0;
    int sum=0;
    for(int i=0;i<n;i++){
    
    
        sum+=c[i].b;
        q.push(c[i]);
        while(sum>c[i].d){
    
    
            node t=q.top();
            q.pop();
            if(t.b>sum-c[i].d){
    
    
                t.b-=sum-c[i].d;
                res+=(sum-c[i].d)*1.0/t.a;
                sum=c[i].d;
                q.push(t);
            }
            else{
    
    
                sum-=t.b;
                res+=t.b*1.0/t.a;
            }
        }
    }
    printf("%.2f\n",res);
    return 0;
}

2.2线性表

Book Pile 赛区/题库 SGU 271

在这里插入图片描述

#include<iostream>
#include<cstdio>
#include<cstring>
#include<bitset>
#include<deque>
using namespace std;

int main(){
    
    
    int n,m,k;
    scanf("%d%d%d",&n,&m,&k);
    deque<string>q1;
    deque<string>q2;
    int dir=1;
    for(int i=0;i<n;i++){
    
    
        char str[15];
        scanf("%s",str);
        if(q1.size()>=k) q2.push_back(string(str));
        else q1.push_back(string(str));
    }
    for(int i=0;i<m;i++){
    
    
        char str[50];
        scanf("%s",str);
        if(str[0]=='A'){
    
    
            string s="";
            int len=strlen(str);
            int ok=0;
            for(int j=0;j<len;j++){
    
    
                if(str[j]=='(') ok=1;
                else if(str[j]==')') ok=0;
                else if(ok) s+=str[j];
            }
            if(dir) q1.push_front(s);
            else q1.push_back(s);
            if(q1.size()>k){
    
    
                if(dir){
    
    
                    q2.push_front(q1.back());
                    q1.pop_back();
                }else{
    
    
                    q2.push_front(q1.front());
                    q1.pop_front();
                }
            }
        }else dir = !dir;
    }
    if(dir){
    
    
        while(!q1.empty()){
    
    
            cout<<q1.front()<<endl;
            q1.pop_front();
        }
    }else{
    
    
        while(!q1.empty()){
    
    
            cout<<q1.back()<<endl;
            q1.pop_back();
        }
    }
    while(!q2.empty()){
    
    
        cout<<q2.front()<<endl;
        q2.pop_front();
    }
    return 0;
}

2.4并查集

Feel Good 赛区/题库 NEERC 2005

在这里插入图片描述

  • 【题意概述】
  • 给定一个序列A[n],设Sum[l,r]=​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​ 在这里插入图片描述A[i],Min [l,r[=min(A[l],A[l+1],…,A[r])
  • 求一段区间[l…r],使得Sum[l,r]*Min[l,r]最大
  • 【算法分析】
  • 使用并查集维护区间的和。具体方法为,按A[i]从大到小遍历元素进行合并,如果当前A[i]左边或者右边的元素(区间中最小的数)不小于A[i]则和对应的区间合并,更新区间和以及区间中最小的数,然后用A[i]乘所在区间的和更新答案即可。
#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+5;
typedef long long ll;
ll sum[maxn],a[maxn],L[maxn],R[maxn],ans,ansl,ansr;
int n,flg;
int main(void){
    
    
    while(~scanf("%d",&n)){
    
    
        if(flg)printf("\n");
        else
            flg=1;
        a[0]=a[n+1]=-1;
        for(int i=1;i<=n;i++)
            scanf("%lld",&a[i]),sum[i]=sum[i-1]+a[i],L[i]=R[i]=i;
        //左边这个数≥当前数,那么当前数的左边界必能扩展到左边这个数的左边界,右边界同理
        for(int i=1;i<=n;i++)
            while(a[L[i]-1]>=a[i])
                L[i]=L[L[i]-1];
        for(int i=n;i>=1;i--)
            while(a[R[i]+1]>=a[i])
                R[i]=R[R[i]+1];
        ans=a[1]*a[1];
        ansl=ansr=1;
        for(int i=1;i<=n;i++){
    
    
            ll tmp=a[i]*(sum[R[i]]-sum[L[i]-1]);
            if(tmp>ans||(tmp==ans&&R-L>R[i]-L[i]))
                ans=tmp,ansl=L[i],ansr=R[i];
        }
        printf("%lld\n%lld %lld\n",ans,ansl,ansr);
    }
    return 0;
}


2.5排序

Inversion 赛区/题库 SGU 180

在这里插入图片描述

  • 【算法分析】
  • 题目即要求逆序对的个数,这是一个非常经典的问题,可以用基于数据结构的方法来解决,也可以巧妙的利用排序时序的性质来解决它
  • 方法一:数据结构。从左到右扫描A数组。对于第i个位置,在A[I]到A[-1]中用数据结构统计比A[I]的数的个数,这里可以用到线段树,数状数组实现,时间复杂度为o(NlogN)
  • 方法二归并排序。假设现在要合并A和B两个子区间。根据归并算法,A和B是相邻的,假设A在B左边现在正在比较A[i]和B[i]而且A[i]>B[j],那么A[i],A[i+1]一直到A[A.length]都与B[j]构成逆序对。因为在归并过程中对于归并排序的对于每个B[i]都会找到第一个对于它的A[i]。时间复杂度为:o(NlogN)
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define N 65540
#define ll long long
int n;
int c[N];
struct node{
    
    
    int id;
    int v;
}a[N];
node b[N];
int cmp(node a,node b){
    
    
    return a.v<b.v;
}
int cmp1(node a,node b){
    
    
    return a.id<b.id;
}

int lowbit(int i){
    
    
    return i&(-i);
}
void update(int x,int v){
    
    
    for(int i=x;i<=n;i+=lowbit(i)){
    
    
        c[i]+=v;
    }
}
ll getsum(int x){
    
    
    ll sum=0;
    //此处注意需要计算的是比自己小的,所以不包含自己
    for(int i=x-1;i>=1;i-=lowbit(i)){
    
    
        sum+=c[i];
    }
    return sum;
}
int main(){
    
    
    while(scanf("%d",&n)!=EOF){
    
    
        memset(c,0,sizeof(c));
        for(int i=1;i<=n;i++){
    
    
            scanf("%d",&a[i].v);
            a[i].id=i;
        }
        sort(a+1,a+n+1,cmp);
        a[0].v=-1;
        int k=1;
        //注意离散化的时候考虑相等的情况
        for(int i=1;i<=n;i++){
    
    
            if(a[i].v!=a[i-1].v){
    
    
                b[i].id=a[i].id;
                b[i].v=k++;
            }
            else{
    
    
                b[i].id=a[i].id;
                b[i].v=b[i-1].v;
            }
        }
        sort(b+1,b+n+1,cmp1);
        ll sum=0;
        for(int i=n;i>=1;i--){
    
    
            sum+=getsum(b[i].v);
            update(b[i].v,1);
        }
        printf("%lld\n",sum);
    }
    return 0;
}

2.8线段树

Dynamic Rankings 赛区/题库 ZOJ 2112

在这里插入图片描述

  • 【题意概述】
  • 维护一个长度为n的数列,有m次如下操作:
  • (1)询问一段区间aiai+1…aj第k小的数
  • (2)修改一个元素的值ai=k
  • 【算法分析】
  • 建立一个线段树,维护区间[1…n],线段树的节点是一颗平衡树,平衡树保存这一段区间所有的值,维护它们的大小顺序。这样应该元素ai都会在每一层线段树中包含i的区间对应的平衡树中出现一次,线段树一共O(logn)层,则框架的理论需要求是O(nlogn)因为线段树可以把一段区间分解为O(logn)段子区间,而子区间对应的平衡树可以快速求出比某个值小的数有多少个,所以这样的一个线段树套平衡树就能在O(log2n)的时间内求出区间内比一个特定的数x小或者相等的数的个数是多少。然后通过二分得答案。时间复杂度O(m log 3 n)
#include<vector>
#include<cstdio>
#include<cstring>
#include<algorithm>

using namespace std;
typedef long long ll;
#define maxn  2600005
#define lowbit(x) (x&-x)

int a[60005], b[60005], rt[60005], num, f[60005], used[60005];
int cnt, sum[maxn], ls[maxn], rs[maxn], n, q[10005][4];
void build(int &id, int l, int r){
    
    
    id = ++cnt;
    sum[id]=0;
    if (l == r)
        return;
    int mid = (l + r) / 2;
    build(ls[id], l, mid);
    build(rs[id], mid + 1, r);
}
void update(int &id, int l, int r, int last, int val, int k){
    
    
    id = ++cnt;
    ls[id] = ls[last];
    rs[id] = rs[last];
    sum[id] = sum[last] + k;
    if (l == r)
        return;
    int mid = (l + r) / 2;
    if (val <= mid)
        update(ls[id], l, mid, ls[last], val, k);
    else
        update(rs[id], mid + 1, r, rs[last], val, k);
}
int sm(int x){
    
    
    int ans = 0;
    while (x){
    
    
        ans += sum[ls[used[x]]];
        x -= lowbit(x);
    }
    return ans;
}
int query(int st, int ed, int val){
    
    
    int i, les, res;
    int l = 1, r = num;
    les = rt[st];res = rt[ed];
    for (i = st;i > 0;i -= lowbit(i))
        used[i] = f[i];
    for (i = ed;i > 0;i -= lowbit(i))
        used[i] = f[i];
    while (l < r){
    
    
        int mid = (l + r) / 2;
        int tmp = sm(ed) - sm(st) + sum[ls[res]] - sum[ls[les]];
        if (val <= tmp){
    
    
            r = mid;
            for (i = st;i > 0;i -= lowbit(i))
                used[i] = ls[used[i]];
            for (i = ed;i > 0;i -= lowbit(i))
                used[i] = ls[used[i]];
            les = ls[les];res = ls[res];
        }
        else{
    
    
            l = mid + 1;val -= tmp;
            for (i = st;i > 0;i -= lowbit(i))
                used[i] = rs[used[i]];
            for (i = ed;i > 0;i -= lowbit(i))
                used[i] = rs[used[i]];
            les = rs[les];res = rs[res];
        }
    }
    return l;
}
void add(int x, int v, int k){
    
    
    while (x <= n){
    
    
        int tmp = f[x];
        update(f[x], 1, num, tmp, v, k);
        x += lowbit(x);
    }
}
int main(){
    
    
    char c;
    int T, m, i, j;
    scanf("%d", &T);
    while (T--){
    
    
        cnt = 0;
        memset(sum, 0, sizeof(sum));
        scanf("%d%d", &n, &m);
        for (i = 1;i <= n;i++)
            scanf("%d", &a[i]), b[i] = a[i];
        num = n;
        for (i = 1;i <= m;i++){
    
    
            scanf(" %c", &c);
            if (c == 'Q'){
    
    
                scanf("%d%d%d", &q[i][1], &q[i][2], &q[i][3]);
                q[i][0] = 1;
            }
            else{
    
    
                scanf("%d%d", &q[i][1], &q[i][2]);
                b[++num] = q[i][2];q[i][0] = 0;
            }
        }
        sort(b + 1, b + num + 1);
        num = unique(b + 1, b + num + 1) - b - 1;
        for (i = 1;i <= num;i++)
            a[i] = lower_bound(b + 1, b + num + 1, a[i]) - b;
        build(rt[0], 1, num);
        for (i = 1;i <= n;i++)
            update(rt[i], 1, num, rt[i - 1], a[i], 1);
        for (i = 1;i <= n;i++)
            f[i] = rt[0];
        for (i = 1;i <= m;i++){
    
    
            if (q[i][0] == 1){
    
    
                int ans = query(q[i][1]-1, q[i][2], q[i][3]);
                printf("%d\n", b[ans]);
            }
            else{
    
    
                int tmp;
                add(q[i][1], a[q[i][1]], -1);
                tmp = lower_bound(b + 1, b + num + 1, q[i][2]) - b;
                add(q[i][1], tmp, 1);
                a[q[i][1]] = tmp;
            }
        }
    }
    return 0;
}

2.10 平衡树

Treediff 赛区/题库 SGU 507

在这里插入图片描述

  • 【算法分析】
  • 有mp维护子树中叶节点的值,每插入一个值时,用第一个比他小的值和第一个比他大的值和他作差,更新答案。
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 5e4+10;
const ll inf = (1LL<<31)-1;
int n,m,val[N];
ll ans[N];
struct node{
    
    
    int to,nex;
}g[N];
int head[N],cnt;
void add(int u,int v){
    
    
    g[++cnt].to=v;
    g[cnt].nex=head[u];
    head[u]=cnt;
}
int sz[N],son[N],SON,limit,siz;
map<int,int> mp;
map<int,int>::iterator it;
void getsz(int u){
    
    
    sz[u]=1;
    int mx=-1;
    for(int i=head[u];i;i=g[i].nex){
    
    
        getsz(g[i].to);
        if(sz[g[i].to]>mx){
    
    
            mx=sz[g[i].to];
            son[u]=g[i].to;
        }
        sz[u]+=sz[g[i].to];
    }
}
ll mn=inf;
void dfs1(int u){
    
    
    if(u>=limit){
    
    
        mp[val[u]]++;
        if(mp[val[u]]==1){
    
    
            it=mp.find(val[u]);
            if(it!=mp.begin()){
    
    
                it--;
                mn=min(mn,0LL+val[u]-(*it).first);
                it++;
            }
            it++;
            if(it!=mp.end()) mn=min(mn,0LL+(*it).first-val[u]);
        }else mn=0;
    }
    for(int i=head[u];i;i=g[i].nex)
        if(g[i].to!=SON)
            dfs1(g[i].to);

}
void dfs(int u,bool keep){
    
    
    for(int i=head[u];i;i=g[i].nex){
    
    
        if(g[i].to!=son[u]){
    
    
            dfs(g[i].to,0);
        }
    }
    if(son[u]){
    
    
        dfs(son[u],1);
        SON=son[u];
    }
    dfs1(u);
    SON=0;
    if(u<limit) ans[u]=mn;
    if(!keep){
    
    
        mp.clear();
        mn=inf;
    }
}
int main(void){
    
    
    scanf("%d%d",&n,&m);
    limit=n-m+1;
    ans[1]=inf;
    for(int i=2;i<=n;i++){
    
    
        int u;
        scanf("%d",&u);
        add(u,i);
        ans[i]=inf;
    }
    for(int i=limit;i<=n;i++)
        scanf("%d",&val[i]);
    getsz(1);
    dfs(1,0);

    printf("%lld",ans[1]);
    for(int i=2;i<limit;i++)
        printf(" %lld",ans[i]);
    return 0;
} 

2.11 动态树

OTOCI 赛区/题库 SPOJ 4155

在这里插入图片描述

  • 【题意概述】
  • 有N个岛,初始状态下没过岛上各有若干只企鹅,岛与到之间不能相连。
  • 现在又一家旅行社组织游客在这N个岛之间进行参观,接下来会发生Q件事件,事件有如下三件事:
  • Bridge x y:表示查询岛x和岛y之间是否存在一条路径, 如果存在输出“no”;否则输出“yes”,并在岛x和岛y之间建立一座桥使得它们相连。
  • Penguins x y: 表示岛x上的企鹅数变成y。
  • Excursion x y:表示查询岛x到岛y路径上企鹅总数,如果存在路径输出答案,否则输出“imposslible”
  • 【算法分析】
  • 这是一个金典的动态树问题。可以用Link-Cut Tree来维护一个森林
  • Link_Cut Tree对于每个节点定义一个“偏好儿子” 为最近一次访问的儿子,(最近一次被访问的节点没有偏好儿子),“偏好边”为每个节点到偏好儿子的边,“偏好路径” 为连续的偏好边组成的路径。由于访问节点后偏好路径会改变,所以每条偏好路径必须要用Splay维护区间和
  • 每次Link-Cut Tree 的Exposed操作是对于某个节点的访问,找到一条从它原树根的路径
#include <bits/stdc++.h>
using namespace std;
const int N = 30000;

int n, m, u, v;
char ch[20];
struct Link_Cut_Tree {
    
    
    int ch[N+5][2], val[N+5], sum[N+5], pre[N+5], rev[N+5], isrt[N+5];
    Link_Cut_Tree () {
    
    
        for (int i = 1; i <= N; i++)
            isrt[i] = 1;
    }
    void pushdown(int o) {
    
    
        if (rev[o] == 0)
            return;
        int ls = ch[o][0], rs = ch[o][1];
        swap(ch[ls][0], ch[ls][1]), swap(ch[rs][0], ch[rs][1]);
        rev[ls] ^= 1, rev[rs] ^= 1, rev[o] = 0;
    }
    void pushup(int o) {
    
    
        sum[o] = sum[ch[o][0]]+sum[ch[o][1]]+val[o];
    }
    void push(int o) {
    
    
        if (isrt[o] == 0) push(pre[o]);
        pushdown(o);
    }
    void rotate(int o, int kind) {
    
    
        int p = pre[o];
        ch[p][!kind] = ch[o][kind], pre[ch[o][kind]] = p;
        if (isrt[p])
            isrt[p] = 0, isrt[o] = 1;
        else
            ch[pre[p]][ch[pre[p]][1] == p] = o;
        pre[o] = pre[p];
        ch[o][kind] = p, pre[p] = o;
        pushup(p), pushup(o);
    }
    void splay(int o) {
    
    
        push(o);
        while (isrt[o] == 0) {
    
    
            if (isrt[pre[o]])
                rotate(o, ch[pre[o]][0] == o);
            else {
    
    
                int p = pre[o], kind = ch[pre[p]][0] == p;
                if (ch[p][kind] == o) 
                    rotate(o, !kind), rotate(o, kind);
                else 
                    rotate(p, kind), rotate(o, kind);
            }
        }
    }
    void access(int o) {
    
    
        int y = 0;
        while (o) {
    
    
            splay(o);
            isrt[ch[o][1]] = 1, isrt[ch[o][1] = y] = 0;
            pushup(o), o = pre[y = o];
        }
    }
    void makeroot(int o) {
    
    
        access(o);
        splay(o);
        rev[o] ^=1,
        swap(ch[o][0], ch[o][1]);
    }
    void split(int x, int y) {
    
    
        makeroot(x);
        access(y);
        splay(y);
    }
    void link(int x, int y) {
    
    
        makeroot(x);
        pre[x] = y;
    }
    void cut(int x, int y) {
    
    
        split(x, y);
        ch[y][0] = pre[x] = 0,
        isrt[x] = 1;
        pushup(y); }
    void update(int x, int key) {
    
    
        makeroot(x);
        val[x] = key;
        pushup(x);
    }
    int query(int x, int y) {
    
    
        split(x, y);
        return sum[y];
    }
    int find(int x) {
    
    
        access(x), splay(x);
        while (ch[x][0]) x = ch[x][0];
        return x;
    }
}T;

void work() {
    
    
    scanf("%d", &n);
    for (int i = 1; i <= n; i++)
        scanf("%d", &T.val[i]);
    scanf("%d", &m);
    while (m--) {
    
    
        scanf("%s%d%d", ch, &u, &v);
        if (ch[0] == 'b') {
    
    
            if (T.find(u)^T.find(v)) {
    
    
                puts("yes");
                T.link(u, v);
            }
            else puts("no");
        }else
            if (ch[0] == 'p')
                T.update(u, v);
        else {
    
    
            if (T.find(u)^T.find(v))
                puts("impossible");
            else
                printf("%d\n", T.query(u, v));
        }
    }
}
int main() {
    
    
    work();
    return 0;
}

第三章 图论

3.1.2 欧拉路

Strange Graph 赛区/题库 SGU 156

请添加图片描述

  • 【算法分析】
  • 这道题有两种点
    1. 度数>2 在团中的点,一定连接一个度数为2的点
    1. 度数等于2,连接两个团或者附着在一个团上的点,明显度数为2的点的两条边都是要走的,度数>2的点与度数2的点一一对应,所用的边也可以一一对应,所以这道哈密尔顿回路可以转化成欧拉回路
  • 方法:第一种:建立新图,简单清晰
  • 第二种:采用欧拉路的思想后续遍历,关键在怎样选取起点终点使得点迹可以形成回路度数为2的点两条边肯定都要走完,考虑度数大于2的点:如果这时对应的度数为2的点没有走,那么先去度数为2的点,否则:因为路径必须为团外-团内-团外(如果在团内有多步,那么度数就不平衡)所以要标注上一步是在团外还是团内,如果上一步是在团外,那么这一步就可以选择一个团内的度数大于2的点
  • 注意:一个度数大于2的团内点对应1个度数等于2的团外点,但是1个团外点对应2个团内点,所以不能直接用对应点是否已经访问作为团内点遍历条件
#include  <cstdio>
#include <cstring>

using namespace std;
const int maxn=1e4+5;
const int maxm=1e5+5;

int n,m,start;
int first[maxn];
int from[2*maxm],to[2*maxm];
int nxt[2*maxm];
int deg[maxn];
int len[maxn];
bool vis[maxn];
int two[maxn];
int ans[maxn],alen;

void addedge(int f,int t,int ind){
    
    
        nxt[ind]=first[f];
        to[ind]=t;
        from[ind]=f;
        first[f]=ind;
        deg[f]++;
}
void collect(int s,int f){
    
    
        vis[s]=true;
        for(int p=first[s];p!=0;p=nxt[p]){
    
    
                int t=to[p];
                if(deg[t]==2){
    
    
                        two[s]=t;
                }
                else if(deg[t]>2){
    
    
                        if(!vis[t]){
    
    
                                collect(t,f);
                        }
                }
        }
        len[f]++;
}
void dfs(int s,bool in){
    
    
        vis[s]=true;
        if(deg[s]>2&&!vis[two[s]]){
    
    
                dfs(two[s],false);
        }
        for(int p=first[s];p!=0;p=nxt[p]){
    
    
                int t=to[p];
                if(vis[t])continue;
                if(deg[s]==2){
    
    
                        dfs(t,false);
                }
                else if(!in&&deg[t]>2){
    
    
                        dfs(t,true);
                }
        }
        ans[alen++]=s;
}

int main(){
    
    
        scanf("%d%d",&n,&m);
        for(int i=1;i<=m;i++){
    
    
                int f,t;
                scanf("%d %d",&f,&t);
                addedge(f,t,2*i);
                addedge(t,f,2*i+1);
        }
        for(int i=1;i<=n;i++){
    
    
                if(deg[i]>2&&!vis[i]){
    
    
                        collect(i,i);
                        if((len[i]&1)!=0){
    
    
                                puts("-1");
                                return 0;
                        }
                }
        }
        memset(vis,0,sizeof(vis));

        dfs(1,0);
        if(alen!=n){
    
    
                puts("-1");
                return 0;
        }
        for(int i=0;i<n;i++){
    
    
                printf("%d ",ans[i]);
        }
        puts("");
        return 0;
}

猜你喜欢

转载自blog.csdn.net/weixin_47174945/article/details/127187730