luogu.org 试炼场关卡3-11 较复杂图论I

p1322 关押罪犯 判定二分图 || 并查集

这道题是真的好,有两种方法可以写。

题目

https://www.luogu.org/problemnew/show/P1525

解题方法

二分图匹配

题解

1.考虑这样一个判定问题:是否存在一种分配罪犯的方案,使Z市市长看到的那个冲突事件的影响力不超过mid。
2.显然(讨厌看到这两个字 )当mid较小时可行的方案,对于较大的mid仍然可行。
3.换言之,本题的答案具有单调性,可以通过二分法,把求最值的问题转化为判定问题。
4.下面是具体步骤:
(1).二分答案,(然而我还不知道什么是二分答案),设当前二分的值为mid。
(2).此时,任意两个仇恨程度大于mid的罪犯都必须被安排在不同的监狱。
(3).我们把罪犯作为节点,在仇恨程度大于mid的两个监狱之间连边,得到一张无向图。这张无向图的节点需要被分成两个集合(即两个监狱),并且每个集合内部没有边(同一个监狱内没有仇恨程度大于mid的罪犯)。
(4).所以,我们用染色法判定无向图是否为二分图即可。若是二分图,则令二分上界r=mid,否则二分下界l=mid+1。(摘抄自 李煜东《算法竞赛进阶指南》)

代码

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int __=2e4+1;
const int inf=1e9;
char buf[1<<15],*fs,*ft;
inline char getc() { return (ft==fs&&(ft=(fs=buf)+fread(buf,1,1<<15,stdin),ft==fs))?0:*fs++; }
inline int read()
{
	int x=0,f=1;
	char ch=getc();
	while(!isdigit(ch)) { if(ch=='-') f=-1; ch=getc(); }
	while(isdigit(ch)) { x=(x<<1)+(x<<3)+(ch^48); ch=getc(); }
	return x*f;
}
void put(int x)
{
	if(x==0)
	{
		putchar('0'),putchar('\n');
		return;
	}
	if(x<0) putchar('-'),x=-x;
	int num=0;
	char ch[16];
	while(x) ch[++num]=x%10+'0',x/=10;
	while(num) putchar(ch[num--]);
	putchar('\n');
}
struct rec
{
	int ver,edge;
};
vector<rec> a[__];
int color[__],vis[__];
int ok,mid;
void dfs(int x)
{
	if (!ok) return ;
	for (int i=0;i<a[x].size();++i)
	{
		int y=a[x][i].ver,z=a[x][i].edge;
		if (z<=mid) continue;
		if (!color[y])
		{
			color[y]=3-color[x];
			dfs(y);
		}
		else if (color[y]==color[x])
		{
			ok=0;
			return ;
		}
	}
}
int main()
{
	int n=read(),m=read();
	int l=inf,r=-inf;
	for (int i=1;i<=m;++i)
	{
		int x=read(),y=read(),z=read();
		rec E;
		E.edge=z;
		E.ver=y,a[x].push_back(E);
		E.ver=x,a[y].push_back(E);
		l=min(z,l);
		r=max(z,r);
	}
	if (l==r) l=0;
	while (l<r)
	{
		mid=(l+r)>>1;
		memset(color,0,sizeof(color));
		memset(vis,0,sizeof(vis));
		ok=1;
		for (int i=1;i<=n;++i)
		{
			if (!color[i])
			{
				color[i]=1;
				dfs(i);
			}
			if (!ok) break;
		}
		if (ok) r=mid;
		else l=mid+1;
	}
	put(l);
	return 0;
}

小结

额,该怎么说,写了半天,交到洛谷上,MLE掉了,交到本校OJ上,RE了。
然后改了半天,对着洛谷上的题解改,然后被T掉了四组(洛谷和本校OJ上提交的结果出奇的一致),最令人震惊的是,我用VECTOR定义了个数组,替换了邻接表的四个数组后,全AC了,这是什么鬼啊!!!!
总之,很生气。

好了,下面我要尝试一下用并查集写这道题了。

并查集

题解

毕竟只是一道很淳朴的并查集,没有设置坑,(谁说的?那个输出0的,不算是坑吗?但是数据没有卡0 = =),好吧,看模板吧,(然而我还是看完题解才写,打完后又RE了,有照着题解对对,才A掉,我好菜啊,真的是机房最菜啊!!!

#include<bits/stdc++.h>
using namespace std;
const int __=2e4+10;
inline int read()
{
	int f=1,num=0;
	char ch=getchar();
	while (!isdigit(ch)) { if (ch=='-') f=-1; ch=getchar(); }
	while (isdigit(ch)) num=(num<<1)+(num<<3)+(ch^48), ch=getchar();
	return num*f;
}
struct rec//结构体更有利于排序 
{
	int x,y,z;
}f[100010];
int fa[__],enemy[__];
bool operator < (rec a,rec b)//重载小于号 
{
	return a.z>b.z;
}
int get(int x)//俗称“找爸爸” 
{
	return (x==fa[x])?x:fa[x]=get(fa[x]);
}
void conbine(int x,int y)//合并 
{
	fa[get(x)]=get(y);
	return ;
}
bool search(int x,int y)//查找 
{
	if (get(x)==get(y))
		return true;
	else
		return false;
}
//以上均是板子(好吧,下面也是板子魔改而已) 
 
int main()
{
	int n=read(),m=read();
	for  (int i=1;i<=n;++i)
		fa[i]=i;//并查集初始化,差点忘了 
	for (int i=1;i<=m;++i)
		f[i].x=read(),f[i].y=read(),f[i].z=read();
	sort(f+1,f+m+1);//STL实在是省了太多事了,这也是c党自豪的地方
	for (int i=1;i<=m+1;++i)
	/*	
		为什么m+1呢?
		如果运行到m+1会输出0,想想为什么?下面解答: 
		因为如果前面运行m次,都没有两个罪犯在同一监狱
		就会运行到m+1次,而f【m+1】.z=0
		因为题上要求:如果本年内监狱中未发生任何冲突事件,请输出0
	*/ 
	{
		if (search(f[i].x,f[i].y))
		{//如果两个罪犯已经在一个监狱,输出结果
			printf("%d\n",f[i].z);
			exit(0);
		}
		else
		{
			if (!enemy[ f[i].x ])//标记敌人 
				enemy[ f[i].x ]=f[i].y;
			else
				conbine( enemy[ f[i].x ] , f[i].y);//将敌人的敌人合并 
			
			if (!enemy[ f[i].y ])
				enemy[ f[i].y ]=f[i].x;
			else
				conbine( enemy[ f[i].y ] , f[i].x);
		}
	}
	return 0;
}

平凡的世界铸造不平凡的人生——路遥《平凡的世界》

P1113 杂务 topsort || dp

题目

https://www.luogu.org/problemnew/show/P1113

解题方法

topsort

评测结果:
https://www.luogu.org/recordnew/show/15265079

#include
#define up(i,a,b) for (register int i=a;i<=b;++i)
#define down(i,a,b) for (register int i=a;i>=b;--i)
using namespace std;
const int maxn=1e7+5;
inline int read()
{
	int f=1,num=0;	char ch=getchar();
	while (!isdigit(ch)) { if (ch=='-') f=-1; ch=getchar(); }
	while (isdigit(ch)) num=(num<<1)+(num<<3)+(ch^48), ch=getchar();
	return num*f;
}
int n,end[maxn],t[maxn];//开数组记录节点结束与开始时间
int ver[maxn],Next[maxn],head[maxn],In[maxn],len;
void add(int x,int y)
{
    ver[++len]=y,Next[len]=head[x],head[x]=len,++In[y];
}
queue<int>q;
inline int topsort()
{
    memset(end,0,sizeof(end));
    int ans=0;
    up(i,1,n)
        if (!In[i])
            q.push(i);//topsort即从入度为零的点开始遍历
    while (!q.empty())
    {
        int x=q.front();
        q.pop();
        end[x]+=t[x];//出队列时更新节点结束时间(加上完成杂务的时间)
        ans=max(ans,end[x]);//topsort后选数组最大值输出
        for (int i=head[x];i;i=Next[i])
        {
        	int y=ver[i];
            --In[y];//防止走重复的边(毁图大法好)
            end[y]=max(end[y],end[x]);
//			被更新节点开始时间为原被更新节点开始时间与前驱节点结束时间的较大值
            if (!In[y]) q.push(y);//topsort即从入度为零的点开始遍历
        }
    }
    return ans;
}
int main()
{
    n=read();
    up(i,1,n)
    {
        int x=read();
		t[x]=read();//入队列前不断更新节点开始时间
		int y=read();//必须完成的准备工作
        while (y)//y以0结束
        {
        	add(y,x);//建立一个由y到x的有向图,因为只有完成了准备工作才能完成杂务
        	y=read();
        }
    }
    printf("%d",topsort());
    return 0;
}

dp

评测结果:
https://www.luogu.org/recordnew/show/15265072

#include 
using namespace std;
#define up(i,a,b) for (register int i=a;i<=b;++i)
#define down(i,a,b) for (register int i=a;i>=b;--i)
using namespace std;
const int maxn=1e4+10;
inline int read()
{
	int f=1,num=0;	char ch=getchar();
	while (!isdigit(ch)) { if (ch=='-') f=-1; ch=getchar(); }
	while (isdigit(ch)) num=(num<<1)+(num<<3)+(ch^48), ch=getchar();
	return num*f;
}
int f[maxn],ans;
int main()
{
    int n=read();
    up(i,1,n)
	{
        int k=read(),t=read();//其实这个k并没有什么用
        while ((k=read()) && k)//当k等于0就会跳出循环
            f[i]=max(f[k],f[i]);
//		从这件事所有的**前提中(可同时完成)**找到一个最迟完成的,这就是这件事的最早起始时间
        f[i]+=t;//然后加上完成这件事所需要的时间就是这件事最早完成的时间
        ans=max(ans,f[i]);//统计答案
    }
    printf("%d", ans);
    return 0;
}

dp真是快的不行!!!

P1268 树的重量 思维题目

题目

https://www.luogu.org/problemnew/show/P1268

代码

#include
using namespace std;
const int maxn=100;
inline int read()
{
	int f=1,num=0;	char ch=getchar();
	while (!isdigit(ch)) { if (ch=='-') f=-1; ch=getchar(); }
	while (isdigit(ch)) num=(num<<1)+(num<<3)+(ch^48), ch=getchar();
	return num*f;
}
int n,dist[maxn][maxn];
int main()
{
	while ((n=read()))
	{
		if (!n) break;
		memset(dist,0,sizeof(dist));
		for (int i=1;i<=n;++i)
			for (int j=i+1;j<=n;++j)
				dist[i][j]=read();
		int ans=dist[1][2];
		for (int i=3;i<=n;++i)
        {
            int t=0x7fffffff;
            for (int j=2;j<i;++j)
                t=min(t,(dist[1][i]-dist[1][j]+dist[j][i])/2);
            ans+=t;
        }
		printf("%d\n",ans);
	}
	return 0;
}

所以,实在想不到的话,就看题解,然后搞懂,虽然这种题估计也不会出类似的。(扔下话就逃)

你可能感兴趣的:(二分图,topsort,======DP====,并查集,luogu)