差分约束,拓扑排序与SCC

A:区间选点——(差分约束与spfa)

题目:

给定一个数轴上的 n 个区间,要求在数轴上选取最少的点使得第 i 个区间 [ai, bi] 里至少有 ci 个点

使用差分约束系统的解法解决这道题

输入:

输入第一行一个整数 n 表示区间的个数,接下来的 n 行,每一行两个用空格隔开的整数 a,b 表示区间的左右端点。1 <= n <= 50000, 0 <= ai <= bi <= 50000 并且 1 <= ci <= bi - ai+1。

输出:

输出一个整数表示最少选取的点的个数

输入样例:

5
3 7 3
8 10 3
6 8 1
1 3 1
10 11 1

输出样例:

6
首先说啥是差分约束,大概就是
一种特殊的 n 元一次不等式组,它包含 n个变量以及m个约束条件。
• 每个约束条件是由两个其中的变量做差构成的,形如𝑥i−𝑥j≤𝑐k, 其中𝑐k是常数(可以是非负数,也可以是负数)。
• 我们要解决的问题是:求一组解𝑥1=𝑎1,𝑥2=𝑎2,…,𝑥n=𝑎n,使 得所有的约束条件得到满足,否则判断出无解。
• 注意到,如果{𝑎1,𝑎2,𝑎3,…,𝑎n}是该差分约束系统的一组解,那么对 于任意的常数𝑑,显然{𝑎1+𝑑,𝑎2+𝑑,…,𝑎n+𝑑}也是该差分约束系统一组解。
求解差分约束系统,都可以转化为图论中得单源最短路问题
对于差分约束中的每一个不等式约束Xi-Xj<Ck,都可以移项变形为Xi<=Ck+Xj.如果令Ck=w(i,j) dis[i]=Xi,dis[j]=Xj ,那么原式变为dis[i]<=dis[j]+w(i,j),与最短路中的松弛操作相似。
由于原式为Xi<=Ck+Xj,求解是按照等于求解,跑最短路出来的最大解,如果想要最小解即为Xi>=Ck+Xj,跑最长路。

构造不等式组:
记sum[i]表示数轴上[0,i]之间选点的个数
对于第i个区间[ai,bi]需要满足sum[bi]-sum[ai-1]>=ci
同时需要保证sum[i]是有意义的,
0<=sum[i]-sum[i-1]<=1;
这时就会出现0<=sum[1]-sum[0]<=1; 此时为了方便整体后移改成sum[bi+1]-sum[ai]>=ci ; (可以后移也是基于差分约束系统+d,-d结果不变)
(以上内容搬运自某学长的PPT orz 侵删)
以上搞明白以后,这题就像是一个板子题了,用SPFA求单源最短路

#include <iostream>
#include <queue>
#include<string>
#include<cstring>
#include<string.h>
using namespace std;
const int max1 = 1e8 * 5;
int m;
int head[1000000], dis[10000000], cnt[10001];
bool vis[100000];//, bl[10001];
//int al[1000000];
int max(int a, int b)
{
    if (a > b) return a;
    else return b;
}
struct edge
{
    int to;
    int next;
    int w;
};
int tot;
edge a[10000000];
void add(int x, int y, int w)
{
    a[++tot].to = y;
    a[tot].next = head[x];
    a[tot].w = w;
    head[x] = tot;
}
queue<int> q;
void SPFA()
{
    while (!q.empty()) q.pop();

    memset(vis, 0, sizeof(vis));
    for (int i = 0; i <= m; i++)
    {
        dis[i] = -max1;
    }
    dis[0] = 0;
    vis[0] = 1;
    q.push(0);
    while (!q.empty())
    {
        int t = q.front();
        q.pop();
        vis[t]=0;
        for (int i = head[t]; i; i = a[i].next)
        {
            int y = a[i].to, w = a[i].w;
            if (dis[y] < dis[t] + w)
            {
                dis[y] = dis[t] + w;
                if (!vis[y]) vis[y] = 1, q.push(y);
            }
        }
    }
}
int main()
{
    int n;
    int u, v, w;
    cin >> n;
    m = 0;
    memset(head, 0, sizeof(head));
    while(n--)
    {
        scanf("%d%d%d", &u, &v, &w);
        add(u, v + 1, w);
        m = max(m, v + 1);
    }
    for (int i = 1; i <= m; i++)
    {
        add(i, i - 1, -1);
        add(i - 1, i, 0); 
    }
    tot = 0;
        
    SPFA();

    cout << dis[m] << endl;
    return 0;
}

B:猫猫向前冲——(拓扑排序)

题目:

众所周知, TT 是一位重度爱猫人士,他有一只神奇的魔法猫。
有一天,TT 在 B 站上观看猫猫的比赛。一共有 N 只猫猫,编号依次为1,2,3,…,N进行比赛。比赛结束后,Up 主会为所有的猫猫从前到后依次排名并发放爱吃的小鱼干。不幸的是,此时 TT 的电子设备遭到了宇宙射线的降智打击,一下子都连不上网了,自然也看不到最后的颁奖典礼。
不幸中的万幸,TT 的魔法猫将每场比赛的结果都记录了下来,现在他想编程序确定字典序最小的名次序列,请你帮帮他。

输入:

输入有若干组,每组中的第一行为二个数N(1<=N<=500),M;其中N表示猫猫的个数,M表示接着有M行的输入数据。接下来的M行数据中,每行也有两个整数P1,P2表示即编号为 P1 的猫猫赢了编号为 P2 的猫猫。

输出:

给出一个符合要求的排名。输出时猫猫的编号之间有空格,最后一名后面没有空格!

其他说明:符合条件的排名可能不是唯一的,此时要求输出时编号小的队伍在前;输入数据保证是正确的,即输入数据确保一定能有一个符合要求的排名。

输入样例:

4 3
1 2
2 3
4 3

输出样例:

1 2 4 3

数据结构王老师举得拓扑排序的例子就是这个,拓扑排序本身思想比较简单。找一个入度为0的点。删去这个点和全部边。再找……
题目要求字典序最小。那正统的思路应该是用一个小根堆,每次把入度为零的全扔进去,取堆顶的点删去。
但我暴力居然过了。。。(没用小根堆优化)
我就放暴力的代码了(下次数据不一定善良,请优化)

#include<iostream>
#include<string.h>
using namespace std;
int mint(int a,int b)
{
    if(a>b) return b;
    else return a;
}
int head[1001]/*,vis1[1001]*/,vis[1001],dis1[1001],dis2[1001];
int father1[1001],father2[1001];
int min1(int a,int b)
{
    if(a>b) return b;
    else return a;
}
struct edge
{
    int to;
    int next;
};
int tot;
edge a[1000000];
void add(int x,int y)
{
    a[++tot].to=y;
    a[tot].next=head[x];
    head[x]=tot; 
}
int dil[1000000];
void quxiao(int t)
{
    for (int i = head[t]; i; i = a[i].next)
    {
        int y = a[i].to;
        dil[y]--;
    }
}
int main()
{
    int xulie[100000];
    
    int n,m;
    while(cin>>n)
    {
        memset(vis,0,sizeof(vis));
        memset(dil,0,sizeof(dil));
        memset(head,0,sizeof(head));
        cin>>m;
        for(int i=0;i<m;i++)
        {
            int u,v;
            cin>>u>>v;
            add(u,v);
            dil[v]++;
        }
        int w=0;
        int minc;
        while(w<n)
        {
            minc=1000000;
            for(int i=1;i<=n;i++)
            {
                if(!vis[i]&&dil[i]==0)
                {
                //  vis[i]=1;
                    minc=mint(minc,i);
                }
                
            }
            xulie[w]=minc;
            vis[minc]=1;
            quxiao(minc);
            w++;
        }
        for(int i=0;i<n;i++)
        {
            cout<<xulie[i];
            if(i!=n-1) cout<<" ";
        }
        cout<<endl;
    }
    return 0;
}

C:班长竞选——(SCC 与缩点)

题目:

大学班级选班长,N 个同学均可以发表意见 若意见为 A B 则表示 A 认为 B 合适,意见具有传递性,即 A 认为 B 合适,B 认为 C 合适,则 A 也认为 C 合适 勤劳的 TT 收集了M条意见,想要知道最高票数,并给出一份候选人名单,即所有得票最多的同学,你能帮帮他吗?

输入:

本题有多组数据。第一行 T 表示数据组数。每组数据开始有两个整数 N 和 M (2 <= n <= 5000, 0 <m <= 30000),接下来有 M 行包含两个整数 A 和 B(A != B) 表示 A 认为 B 合适。

输出:

对于每组数据,第一行输出 “Case x: ”,x 表示数据的编号,从1开始,紧跟着是最高的票数。 接下来一行输出得票最多的同学的编号,用空格隔开,不忽略行末空格!

输入样例:

2
4 3
3 2
2 0
2 1

3 3
1 0
2 1
0 2

输出样例:

Case 1: 2
0 1
Case 2: 2
0 1 2

这题就是,SCC+缩点
first,scc(找连通分量。)


image.png

(又不要脸的找来一堆资料,手打确实太麻烦)
其实就是两遍dfs的功夫。第一次确定(逆)后序序列,第二次把图返,然后确定每个点在哪个SCC里面。

第二部,缩点
缩点,每个SCC都作为一个点,连接SCC之间的线,得图G。此时存反图 fG。遍历点得到,每个 SCC中的详细的点。遍历边,然后寻找跨越不同SCC 中的边。
经分析可以知道,最大的票数应该在G 中指向SCC 最多的,但是这样计算不方便,因此用反图fG, 从出度为0的SCC 遍历,获取此SCC所能到达的所有SCC ,统计票数。

#include<iostream>
#include<vector>
#include <algorithm>
#include<string.h>
using namespace std;
const int N=5020;
int len;
int head1[5020],vis[5020];
vector<int> K1[5020],K2[5020],l[5020];
int ret[5020];
int num[5020];
int head2[5020];
struct edge
{
    int to;
    int next;
};
int tot1,tot2;
edge a[30010];
edge b[30010];
void add(int x,int y,edge *a,int *head,int &tot)
{
    a[++tot].to=y;
    a[tot].next=head[x];
    head[x]=tot; 
}
int n,f[N+1];
int dcnt=0,fcnt=0;
void dfs(int x,int *head)
{
    vis[x]=1;
    for (int i = head[x]; i; i = a[i].next)
    {
        int y=a[i].to;
        if(!vis[y])
        {
            dfs(y,head);
        }
    }
    f[x]=fcnt; fcnt++;
}
//int sccnum[5000];
int wk[5000+10];
int scc[5000+10];
int sccn=0;

void dfs2(int x,int &number)
{
    scc[x]=sccn;
    l[sccn].push_back(x);
    number++;
    vis[x]=1;
    for (int i = head2[x]; i; i = b[i].next)
    {
        int y=b[i].to;
        if(!vis[y])
        {
            dfs2(y,number);
        }
    }
}
void suodian()
{
    for(int x=0;x<n;x++)
    {
        for(int i=head1[x];i;i=a[i].next)
        {
            int y=a[i].to;
            if(scc[x]==scc[y])
                continue;
            K1[scc[x]].push_back(scc[y]);   
            K2[scc[y]].push_back(scc[x]);
        } 
    }
}

void dfs3(int x)
{
    vis[x]=1;
    len+=l[x].size();
    for(int i=0;i<K2[x].size();i++){
        if(!vis[K2[x][i]]){
            dfs3(K2[x][i]);
        }           
    }       
}

void solve(int *head)
{
    dcnt=0;fcnt=0;
    memset(vis,0,sizeof(vis));
    for(int i=0;i<n;i++)
    {
        if(!vis[i]) dfs(i,head);
    }
}
void solve2()
{
    sccn=0;
    memset(vis,0,sizeof(vis));
    for(int i=0;i<n;i++)
    {
        int number=0;
        if(!vis[wk[i]]) dfs2(wk[i],number), sccn++;
        
    }
}
int main()
{
    int T;
    cin>>T;
    int w=1;
    while(T--)
    {
        tot1=0,tot2=0;
        //if(w!=1) cout<<endl;
        cout<<"Case "<<w<<": "; w++;
        cin>>n;
    
        memset(num,0,sizeof(num));
        memset(head1,0,sizeof(head1));
        memset(head2,0,sizeof(head2));
        int m;cin>>m;
        int u,v;
        while(m--)
        {
            cin>>u>>v;
            add(u,v,a,head1,tot1);
            add(v,u,b,head2,tot2);
        }
        solve(head1);
        for(int i=0;i<n;i++)
        {
            wk[n-1-f[i]]=i;
        }
    /*  for(int i=0;i<n;i++)
        {
            cout<<f[i]<<" ";
        }
        cout<<endl;
        for(int i=0;i<n;i++)
        {
            cout<<wk[i]<<" ";
        }*/
        solve2();
        suodian();
        int maxans=0;
        
        for(int i=0;i<sccn;i++)
        {
            if(K1[i].size()==0)
            {
                for(int j=0;j<=sccn;j++)
                    vis[j]=0;
                len=0;
                dfs3(i);
                num[i]=len-1;
                maxans=max(maxans,num[i]);
            }
        }   
        cout<<maxans<<endl;
        int dss=0;
        for(int i=0;i<sccn;i++)
        {
            if(num[i]==maxans)
            {
                for(int j=0;j<l[i].size();j++)
                {
                    ret[dss]=l[i][j];
                    dss++;
                }
            }
        }
    //  dss--;
        sort(ret,ret+dss);
        for(int kdd=0;kdd<dss;kdd++)
        {
            cout<<ret[kdd];
            if(kdd!=dss-1) cout<<" ";
        }
        cout<<endl;
    /*  for(int i=0;i<sccn;i++)
        {
            cout<<"n"<<num[i]<<endl;
        }
        for(int i=0;i<n;i++)
        {
            cout<<scc[i]<<" ss";
        }
        cout<<endl;*/
    
    /*  for(int i=0;i<n;i++)
        {
            if(scc[i]==dl)
            {
                cout<<i<<" ";
            }
        }*/
        
        /*cout<<endl;
        for(int i=0;i<n;i++)
        {
            cout<<f[i]<<" ";
        }
        cout<<endl;*/
        
        //cout<<"Case:"<<w<<" "; w++;
        for(int dssl=0;dssl<n;dssl++)
        {
            K1[dssl].clear();K2[dssl].clear();l[dssl].clear();
        }
    }
    return 0;
} 
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,193评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,306评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,130评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,110评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,118评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,085评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,007评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,844评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,283评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,508评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,667评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,395评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,985评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,630评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,797评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,653评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,553评论 2 352

推荐阅读更多精彩内容

  • 在C语言中,五种基本数据类型存储空间长度的排列顺序是: A)char B)char=int<=float C)ch...
    夏天再来阅读 3,340评论 0 2
  • 差分约束 什么是差分约束? 差分约束系统(system of difference constraints),是求...
    opbnbjs阅读 1,042评论 0 3
  • 以西瓜书为主线,以其他书籍作为参考进行补充,例如《统计学习方法》,《PRML》等 第一章 绪论 1.2 基本术语 ...
    danielAck阅读 4,514评论 0 6
  • 作业部分 区间选点 II 题意 给定一个数轴上的 n 个区间,要求在数轴上选取最少的点使得第 i 个区间 [ai,...
    _fallen阅读 350评论 0 0
  • 计算机二级C语言上机题库(南开版) 1.m个人的成绩存放在score数组中,请编写函数fun,它的功能是:将低于平...
    MrSunbeam阅读 6,350评论 1 42