杂题题解(康复训练)

本文探讨了编程竞赛中的若干经典问题,包括矩阵快速幂、2-SAT与差分约束系统的解决策略。通过实例解析了如何运用数学思维解决实际问题,如洛谷上的数学作业与勾股定理题目,以及满汉全席问题的2-SAT求解。同时,文章还介绍了如何将线段树应用于求解复杂问题,以及如何在满汉全席问题中构建并判断2-SAT图的连通性。

目录

关于10.05标准化测试:

        T1:

        题目描述:

        题解:

        T2:

        题目描述:

        前置知识及证明(话说我是真的屑,初中白学了?)


关于10.05标准化测试:

        T1:

                题目描述:

        [HNOI2011]数学作业 - 洛谷https://www.luogu.com.cn/problem/P3216

                题解:

                刚开始的时候比较蒙圈,不晓得应该怎么写,于是就先写了一个暴力。接着就是从简单的入手,发现10以内的递推矩阵比较简单,因而想到可以分段进行矩阵递推

        1~9为一段,10~99为一段,100~999为一段…………

        接下来是如何构造矩阵了,发现单一的一个矩阵并不能满足,所以就采用了分段矩阵,1~9对应一个矩阵,10~99对应一个矩阵,以此类推。

        然后是矩阵的维度问题,开始认为一个二维就可以了,但是二维矩阵不能实现依次+1的效果,所以直接三维矩阵:

1~9:\begin{pmatrix} 10 &0 &0 \\ 1& 1 &0 \\ 0& 1 &1 \end{pmatrix} 10~99:\begin{pmatrix} 100 &0 &0 \\ 1& 1 &0 \\ 0& 1 &1 \end{pmatrix} 100~999:\begin{pmatrix} 1000 &0 &0 \\ 1& 1 &0 \\ 0& 1 &1 \end{pmatrix}…………

        要注意一下对于每一个矩阵的值都要对m取模。

        (自己矩阵每次写都要想很久,想的很慢,所以一下的模式有必要单独提出来,太菜了没办法TAT)

for(int i=0;i<3;i++){
	for(int j=0;j<3;j++){
		for(int k=0;k<3;k++){
			pp[i][j]=(pp[i][j]+ret[i][k]*now[k][j])%m;
		}
	}
}
for(int i=0;i<3;i++)for(int j=0;j<3;j++)ret[i][j]=pp[i][j];
for(int i=0;i<3;i++)for(int j=0;j<3;j++)pp[i][j]=0;

            上标码:

#include<cstdio>

using namespace std;

unsigned long long n,m;
unsigned long long pe[3][3]={{10,0,0},{1,1,0},{0,1,1}};
unsigned long long hk[3][3]={{10,0,0},{1,1,0},{0,1,1}};
unsigned long long ans[3];
unsigned long long sd[3];

inline void qpow(unsigned long long t)
{
	unsigned long long ret[3][3]={{1,0,0},{0,1,0},{0,0,1}};
	unsigned long long now[3][3];
	unsigned long long pp[3][3]={{0,0,0},{0,0,0},{0,0,0}};
	for(int i=0;i<3;i++)for(int j=0;j<3;j++)now[i][j]=pe[i][j];
	while(t){
		if(t&1){
			for(int i=0;i<3;i++){
				for(int j=0;j<3;j++){
					for(int k=0;k<3;k++){
						pp[i][j]=(pp[i][j]+ret[i][k]*now[k][j])%m;
					}
				}
			}
			for(int i=0;i<3;i++)for(int j=0;j<3;j++)ret[i][j]=pp[i][j];
			for(int i=0;i<3;i++)for(int j=0;j<3;j++)pp[i][j]=0;
		}
		for(int i=0;i<3;i++){
				for(int j=0;j<3;j++){
					for(int k=0;k<3;k++){
						pp[i][j]=(pp[i][j]+now[i][k]*now[k][j])%m;
					}
				}
			}
			for(int i=0;i<3;i++)for(int j=0;j<3;j++)now[i][j]=pp[i][j];
			for(int i=0;i<3;i++)for(int j=0;j<3;j++)pp[i][j]=0;
		t>>=1;
	}
	for(int i=0;i<3;i++)for(int j=0;j<3;j++)hk[i][j]=ret[i][j];
}

int main(void)
{
	scanf("%lld%lld",&n,&m);
	unsigned long long tk=9;
	ans[0]=0;ans[2]=1;ans[1]=1;
	sd[0]=0;sd[2]=1;sd[1]=1;
	//n--;
	while(n){
		if(tk>n)tk=n;
		n-=tk;
		for(int i=0;i<3;i++)for(int j=0;j<3;j++)hk[i][j]=pe[i][j];
		qpow(tk);
		sd[0]=(ans[0]*hk[0][0]+ans[1]*hk[1][0]+ans[2]*hk[2][0])%m;
		sd[1]=(ans[0]*hk[0][1]+ans[1]*hk[1][1]+ans[2]*hk[2][1])%m;
		sd[2]=(ans[0]*hk[0][2]+ans[1]*hk[1][2]+ans[2]*hk[2][2])%m;
		ans[0]=sd[0];ans[1]=sd[1];ans[2]=sd[2];
		tk*=10;
		pe[0][0]=(pe[0][0]*10)%m;
	}
	printf("%llu",ans[0]);
	return 0;
}

        T2:

                题目描述:

        [HNOI2011]勾股定理 - 洛谷https://www.luogu.com.cn/problem/P3213

        前置知识及证明(话说我是真的屑,初中白学了?)

A^{2}+B^{2}=C^{2}    令   C=B-K

A^{2}+B^{2}=(B-K)^{2}\rightarrow A^{2}=(B-K)^{2}-B^{2}\rightarrow A^{2}=B^{2}+K^{2}-2BK-B^{2}=K^{2}-2BK

B=\tfrac{K^{2}-A^{2}}{2K},C=B-K=\tfrac{-K^{2}-A^{2}}{2K}

所以有:

        A^{2}+(\tfrac{K^{2}-A^{2}}{2K})^{2}=(\tfrac{-K^{2}-A^{2}}{2K})^{2}\rightarrow (2KA)^{2}+(A^{2}-K^{2})=(A^{2}+K^{2})^{2}

利用这个公式就可便利的求出1e6中所有互质勾股数对

        (K==i,A==j)枚举i j,满足i j不同奇偶,并且gcd(i,j)=1

void preWork()
{
	for(LL i=2;(i<<1)<=M;++i)
		for(LL j=1;(j*i<<1)<=M && j<i ;++j)
			if(i*i-j*j<=M && (i&1)!=(j&1) && gcd(i,j)==1)
			{
				int a=i*i-j*j,b=i*j<<1;
				add(a,b);add(b,a);
			}	
	return ;
}

        其实这道题对于我现在的水平好像就至此为止了,接下来就是优美的  代 码 欣 赏  片段

        尽力写了一些注释,渴望着理解,然后g了

        主函数:

int main()
{
	read(n);
	Mi[0]=1;
	for(int i=1;i<=n;++i) 
	{
		Mi[i]=(Mi[i-1]<<1)%Mod;
		read(H[i]);
		in[H[i]]++;
	}
	sort(H+1,H+1+n);
	preWork();
	LL ans=1;
	for(int i=1;i<=n;i++)
		if(!vis[H[i]])
			ans=ans*solve(H[i])%Mod;
	ans--;
	print(ans);
	return 0;
}

        dfs:求出以为x为根的连通块

void dfs(int x,int f)
{
	vis[x]=1;
	zhan[++sum]=x;//入栈 
	for(int i=h[x];i;i=pre[i])
		if(in[to[i]]&&to[i]!=f)//如果in不等于零即在序列中出现过 并且v!=fath 
		{
			if(!vis[to[i]]) dfs(to[i],x);//未被访问过 
			else out[i]=1,edge[++edge[0]]=i,col[to[i]]=1,col[x]=1;
			//else 执行表示在图里面存在环
			//将此边进行标记,并且记录边的号数。 
		}
}

        DFS:对于每一个连通块进行答案的统计

void DFS(int x,int n) 
{
	if(x>n) 
	{
		for(int i=1;i<=edge[0];i++) 
		{
			int a=to[edge[i]],b=to[edge[i]^1];
			if(must[a]==1 && must[b]==1) return ;
		}
		DP(zhan[1],0);
		Ans=(Ans+dp[zhan[1]][0]+dp[zhan[1]][1])%Mod;
		return ;
	}
	must[w[x]]=1,DFS(x+1,n);
	must[w[x]]=0,DFS(x+1,n);
}

        DP:nb的树上DP,求出独立集的方案数(类似没有上司的舞会)

        设dp[i][0]dp[i][0]表示在ii这棵子树上不选ii的方案数,dp[i][1]dp[i][1]表示在ii这颗子树上选ii的方案数。

        状态转移长这样:

                dp[i][0]=(dp[v_1][0]+F[v_1][1])\times (dp[v_2][0]+dp[v_2][1])\times ...

                        dp[i][1]=(2^{num[i]}-1)\times dp[v_1][0]\times dp[v_2][0]\times ...

        num[i]表示i出现了几次。

        我们发现我们建的图将构成一片森林。

        假设森林里每颗树的根是rt_1,rt_2......rt_k,那么答案就是:

        (dp[rt_1][0]+dp[rt_1][1])\times(dp[rt_2][0]+dp[rt_2][1])\times...之后再减1,即去掉空集。

        这样就是三十分,成功了!

void DP(int x,int f) 
{
	dp[x][0]=1,dp[x][1]=Mi[in[x]]-1;
	if(col[x]&&must[x]==1) dp[x][0]=0;
	if(col[x]&&must[x]==0) dp[x][1]=0;
	if(h[x]==0) return ;
	for(int i=h[x];i;i=pre[i])
		if(out[i]==0&&to[i]!=f)//这条边不是环上的边,并且v!=fath 
		{
			int y=to[i];
			if(in[y]==0) continue;//如果这个点不在序列中,就跳过 
			DP(y,x);
			dp[x][0]=dp[x][0]*(dp[y][0]+dp[y][1])%Mod;
			dp[x][1]=dp[x][1]*dp[y][0]%Mod;
		}
	return ;
}

        完整代码:

        

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
# define M 1000000
# define Mod 1000000007
# define LL long long
using namespace std;
template<typename T>inline void read(T &x)
{
	T ch=getchar(),xx=1;x=0;
	while(!isdigit(ch))xx=ch=='-'?-1:xx,ch=getchar();
	while(isdigit(ch))x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
	x*=xx;
}
template<typename T>inline void print(T x)
{
	if(x<0)putchar('-'),x=-x;
	if(x>9)print(x/10);
	putchar(x%10+'0');
}
int n,H[M+5],in[M+5],Mi[M+5],dp[M+5][2],zhan[M+5],edge[M+5],w[M+5];
int d,h[M+5],to[M+5],pre[M+5];
bool vis[M+5],must[M+5],col[M+5],out[M+5];
int gcd(int a,int b)
{
	if(b==0) return a;
	return gcd(b,a%b);
}
void add(int a,int b)
{
	d++;
	pre[d]=h[a];
	to[d]=b;
	h[a]=d;
}
void DP(int x,int f) 
{
	dp[x][0]=1,dp[x][1]=Mi[in[x]]-1;
	if(col[x]&&must[x]==1) dp[x][0]=0;
	if(col[x]&&must[x]==0) dp[x][1]=0;
	if(h[x]==0) return ;
	for(int i=h[x];i;i=pre[i])
		if(out[i]==0&&to[i]!=f)//这条边不是环上的边,并且v!=fath 
		{
			int y=to[i];
			if(in[y]==0) continue;//如果这个点不在序列中,就跳过 
			DP(y,x);
			dp[x][0]=dp[x][0]*(dp[y][0]+dp[y][1])%Mod;
			dp[x][1]=dp[x][1]*dp[y][0]%Mod;
		}
	return ;
}
int sum;
void dfs(int x,int f)
{
	vis[x]=1;
	zhan[++sum]=x;//入栈 
	for(int i=h[x];i;i=pre[i])
		if(in[to[i]]&&to[i]!=f)//如果in不等于零即在序列中出现过 并且v!=fath 
		{
			if(!vis[to[i]]) dfs(to[i],x);//未被访问过 
			else out[i]=1,edge[++edge[0]]=i,col[to[i]]=1,col[x]=1;
			//else 执行表示在图里面存在环
			//将此边进行标记,并且记录边的号数。 
		}
}
void preWork()
{
	for(LL i=2;(i<<1)<=M;++i)
		for(LL j=1;(j*i<<1)<=M && j<i ;++j)
			if(i*i-j*j<=M && (i&1)!=(j&1) && gcd(i,j)==1)
			{
				int a=i*i-j*j,b=i*j<<1;
				add(a,b);add(b,a);
			}	
	return ;
}
LL Ans;
void DFS(int x,int n) 
{
	if(x>n) 
	{
		for(int i=1;i<=edge[0];i++) 
		{
			int a=to[edge[i]],b=to[edge[i]^1];
			if(must[a]==1 && must[b]==1) return ;
		}
		DP(zhan[1],0);
		Ans=(Ans+dp[zhan[1]][0]+dp[zhan[1]][1])%Mod;
		return ;
	}
	must[w[x]]=1,DFS(x+1,n);
	must[w[x]]=0,DFS(x+1,n);
}
LL solve(int x)
{
	sum=edge[0]=w[0]=0;
	dfs(x,0);
	//得到了栈,即得到了这一个连通块的编号 
	Ans=0;
	for(int i=1;i<=sum;++i)
		if(col[zhan[i]])
			w[++w[0]]=zhan[i];
	DFS(1,w[0]);
	return Ans;
}
int main()
{
	read(n);
	Mi[0]=1;
	for(int i=1;i<=n;++i) 
	{
		Mi[i]=(Mi[i-1]<<1)%Mod;//预处理出2的n次幂
		read(H[i]);
		in[H[i]]++;//是否存在,对于后面的跑图服务
	}
	sort(H+1,H+1+n);
	preWork();
	LL ans=1;
	for(int i=1;i<=n;i++)
		if(!vis[H[i]])
			ans=ans*solve(H[i])%Mod;
	ans--;
	print(ans);
	return 0;
}

关于正解,可以前往这里阅读神仙的题解

关于10.06标准化测试:

        T1:

                题目描述:

https://www.luogu.com.cn/problem/P3217icon-default.png?t=L892https://www.luogu.com.cn/problem/P3217

                题解:

                自己做的时候还是想到了和正解一样的匹配方式,就是n^2求出每一条线段,并且记录中点x,y长度这些信息。但是,DT的把映射写错了,使得匹配过程效率极低,string的不便便在此体现出来。然而正解不用建立映射,而是排序(我没想到QAQ),按照中点坐标,长度进行排序,然后扫一遍,而对于一段中点相等,长度也相等的线段,进行暴力求答案。

                像这样:

sort(ak+1,ak+1+m,tmp);
	for(int i=1,j=1;i<=m;i=j){
		while(j<=m && ak[j].len==ak[i].len && ak[j].midx==ak[i].midx && ak[j].midy==ak[i].midy)j++;
		for(int k=i;k<j;k++){
			for(int l=k+1;l<j;l++)
				ans=max(ans,abs(ak[k].lix*ak[l].liy-ak[k].liy*ak[l].lix)>>1);
		}
	}

                然后就是对于如何快速求出矩形的面积这个问题,记录下矩形四个顶点然后求答案是可以的,但是这里有一个更加高级的操作:求叉积(了解即可,我还是蒙的)

                现在该解决的都解决了,上马!

        

#include<cstdio>
#include<cmath>
#include<algorithm>
#include<cstring>
#include<string>

using namespace std;

struct Node{
	long long lix,liy;
	long long midx,midy;
	long long len;
}ak[2250010];

struct NNN{
	long long x,y;
}a[1510];

int n,m;
long long ans;

template <typename T>inline void in(T &x)
{
	T ch=getchar(),xx=0,fw=1;
	while(!isdigit((int)ch)){if(ch=='-')fw=-1;ch=getchar();}
	while(isdigit((int)ch)){xx=(xx<<1)+(xx<<3)+ch-'0';ch=getchar();}
	x=xx*fw;
}

inline long long getlen(int xx,int yy)
{
	return (a[xx].x-a[yy].x)*(a[xx].x-a[yy].x)+(a[xx].y-a[yy].y)*(a[xx].y-a[yy].y);
}

inline bool tmp(Node aa,Node bb)
{
	return aa.len==bb.len?aa.midx==bb.midx?aa.midy<bb.midy:aa.midx<bb.midx:aa.len>bb.len;
}

int main(void)
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++){
		in(a[i].x);in(a[i].y);
		for(int j=1;j<i;j++){
			m++;
			ak[m].lix=a[i].x-a[j].x;
			ak[m].liy=a[i].y-a[j].y;
			ak[m].midx=a[i].x+a[j].x;
			ak[m].midy=a[i].y+a[j].y;
			ak[m].len=getlen(i,j);
		}
	}
	ans=0;
	sort(ak+1,ak+1+m,tmp);
	for(int i=1,j=1;i<=m;i=j){
		while(j<=m && ak[j].len==ak[i].len && ak[j].midx==ak[i].midx && ak[j].midy==ak[i].midy)j++;
		for(int k=i;k<j;k++){
			for(int l=k+1;l<j;l++)
				ans=max(ans,abs(ak[k].lix*ak[l].liy-ak[k].liy*ak[l].lix)>>1);
		}
	}
	printf("%lld",ans);
}

        T3:

                题目描述:
https://www.luogu.com.cn/problem/P3218icon-default.png?t=L892https://www.luogu.com.cn/problem/P3218

                题解:

                一测拿到题目的时候毫无头绪,然后看了题解之后,发现这道题其实还好,但是有一个大难点就是证明:当每段速度基本相同的时候,答案是最优秀的。here

                题目定义\large av+bs是当前段每单位所耗的油量,s是斜率,所以当为下坡时,这个油耗一定是0(它不能下坡自动加油)而\large a,b都是给出的,对于\large s每一段都是固定的,所以一定满足单调性,说道单调性,那就二分!

                知道了这样的性质之后,就可以直接二分答案,二分出使总油耗不超出油耗的最大速度。

                然而光这样是不行的,因为下坡时满足\large av+bs=0\rightarrow v=\frac{-bs}{a },但是有个问题,就是车子的最大速度是被限制了的,所以在两者之间取小的那个。然而什么时候输出IMPOSSIBLE?就是当二分得到的速度为零,并且道路有上坡的时候(因为如果全是下坡,那就可以不耗油的滑下去)

                该考虑的都考虑了,上马!

                

#include<cstdio>
#include<algorithm>
#include<cmath>
#define emp 1e-15//处理精度

using namespace std;

struct Node{
	double x,y,k,len;
	inline void in(){
		scanf("%lf%lf",&x,&y);
		x=x/1000.0;y=y/1000.0;
		k=(double)y/x;
		len=sqrt((x*x)+(y*y));
	}
}C[10010];

int T;
double a,b,maxv,f;
int r;

inline double maxx(double aa,double bb)
{
	return aa-bb>emp?aa:bb;
}

inline double minn(double aa,double bb)
{
	return aa-bb<emp?aa:bb;
}

inline bool check(double tk)
{
	double ret=0.0;
	for(int i=1;i<=r;i++){
		ret+=maxx(a*tk+b*C[i].k,0)*C[i].len;
		if(ret>f+emp)return false;
	}
	return true;
}

inline double getans(double v)
{
	double ret=0.0;
	for(int i=1;i<=r;i++){
		double now=a*v+b*C[i].k;
		if(now>emp){
			if(v<=emp)return 0;
			ret+=(double)C[i].len/v;
		}else{
			double vv=(double)(-1.0*b*C[i].k)/a;
			vv=minn(vv,maxv);
			ret+=C[i].len/vv;
		}
	}
	return ret;
}

inline void solve()
{
	double lef=0.0,rig=maxv;
	int t=0;
	while(t<2000){
		double mid=(lef+rig)/2.0;
		if(check(mid))lef=mid;
		else rig=mid;
		t++;
	}
	double ans=getans(lef);
	if(ans<=emp){
		printf("IMPOSSIBLE\n");
	}else printf("%.5lf\n",ans);
}

int main(void)
{
	scanf("%d",&T);
	while(T--){
		scanf("%lf%lf%lf%lf%d",&a,&b,&maxv,&f,&r);
		for(int i=1;i<=r;i++)C[i].in();
		solve();
	}
}

 关于把一些题转化成图论:

        2-SAT

                先看一道题:

                满汉全席https://www.luogu.com.cn/problem/P4171icon-default.png?t=L892https://www.luogu.com.cn/problem/P4171                这道题就是2-SAT中的典中典,对于每一个评测官,要求必须满足他的两个要求的其中一个,那么一个评测官所有的要求可能情况:hh,hm,mh(怪物猎人 大雾),mm。

                对于hh:

                        如果第一个选了m,那么必须第二个选h才能满足的,所以第一个为m,第二个为汗就被捆绑起立来了,同理,第二个选了m,第一个就得选h,同样捆绑

                对于hm

                        如果第一个选了m,那么必须第二个选m才能满足的,所以第一个为m,第二个为汗就被捆绑起立来了,同理,第二个选了h,第一个就得选h,同样捆绑

                        。。。。。。

                想到这样捆绑,可能想到并查集或者是建图,然后我们发现,对于hh这种情况,第二个选了h的话,第一个不是必须选m,所以这样的捆绑是单向的,图!

                现在,我们的2-SAT就出场了,2-SAT,通俗的讲就是,每个东东会给你两个条件,对于这两个条件,可以是用与、或、非……等连接起来,这道题就是或,即满足一个即可。将hh,hm这些条件进行一个单向边的建,不难看出建出来的图是一个个连通块。

                那么如何判断呢?Tarjan又来了,判断一个食材 的h,和m是不是在同一个连通块里面,不能用简单的DFS染色,会出大问题,那咱们就整个强连通分量。

                这是一个正常的求dfn和low(复习下)

inline void tarjan(int now)
{
	cnt++;
	dfn[now]=low[now]=cnt;
	instack[now]=1;
	sta.push(now);
	for(int i=Head[now];i;i=tr[i].nxt){
		int v=tr[i].v;
		if(!dfn[v]){
			tarjan(v);
			low[now]=min(low[now],low[v]);
		}
		else if(instack[v]){
			low[now]=min(low[now],dfn[v]);
		}
	}
}

                接在这下面的比较核心,用好stack!

        

inline void tarjan(int now)
{
	cnt++;
	dfn[now]=low[now]=cnt;
	instack[now]=1;
	sta.push(now);
	for(int i=Head[now];i;i=tr[i].nxt){
		int v=tr[i].v;
		if(!dfn[v]){
			tarjan(v);
			low[now]=min(low[now],low[v]);
		}
		else if(instack[v]){
			low[now]=min(low[now],dfn[v]);
		}
	}
	if(dfn[now]==low[now]){
		int top;
		coll++;
		do{
			top=sta.top();
			col[top]=coll;
			instack[top]=0;
			sta.pop();
		}while(top!=now);
	}
}

                完整代码:

#include<cstdio>
#include<cstring>
#include<string>
#include<stack>
#include<algorithm>
#include<iostream>

using namespace std;

struct Node{
	int v,nxt;
}tr[2000200];
int Head[2000200];
int kok;

int T,n,m;

int col[2002000];
int cnt;
bool flag;

inline void add(int x,int y)
{
	kok++;
	
	tr[kok].v=y;tr[kok].nxt=Head[x];
	
	Head[x]=kok;
}

int coll=0;
int dfn[2000100],low[2000100];
int instack[2000100];

stack<int>sta;

inline void tarjan(int now)
{
	cnt++;
	dfn[now]=low[now]=cnt;
	instack[now]=1;
	sta.push(now);
	for(int i=Head[now];i;i=tr[i].nxt){
		int v=tr[i].v;
		if(!dfn[v]){
			tarjan(v);
			low[now]=min(low[now],low[v]);
		}
		else if(instack[v]){
			low[now]=min(low[now],dfn[v]);
		}
	}
	if(dfn[now]==low[now]){
		int top;
		coll++;
		do{
			top=sta.top();
			col[top]=coll;
			instack[top]=0;
			sta.pop();
		}while(top!=now);
	}
}

inline void init()
{
	memset(col,0,sizeof(col));
	memset(Head,0,sizeof(Head));
	memset(dfn,0,sizeof(dfn));
	memset(low,0,sizeof(low));
	kok=0;flag=0;cnt=coll=0;
}

int main(void)
{
	scanf("%d",&T);
	char ch1[100],ch2[100];
	while(T--){
		scanf("%d%d",&n,&m);
		init();
		for(int i=1;i<=m;i++){
			cin>>ch1;
			cin>>ch2;
			int x=0,y=0,k;
			k=1; while(ch1[k]>='0'&&ch1[k]<='9')x=x*10+ch1[k++]-'0';
            k=1; while(ch2[k]>='0'&&ch2[k]<='9')y=y*10+ch2[k++]-'0';
			if(ch1[0]=='m'){
				if(ch2[0]=='m'){
					add(x+n,y);
					add(y+n,x);
				}
				else if(ch2[0]=='h'){
					add(x+n,y+n);
					add(y,x);
				}
			}else if(ch1[0]=='h'){
				if(ch2[0]=='m'){
					add(x,y);
					add(y+n,x+n);
				}
				else if(ch2[0]=='h'){
					add(x,y+n);
					add(y,x+n);
				}
			}
		}
		
		for(int i=1;i<=n*2;i++)
			if(!dfn[i])tarjan(i);
	
		for(int i=1;i<=n;i++){
			if(col[i]==col[i+n]){
				flag=1;
				break;
			}
		}
		if(flag)printf("BAD\n");
		else printf("GOOD\n");
	}
}

        差分约束系统:

                对于此类题,我基础不过多叙述,这里是个非常好的详解

                讲讲写差分约束应该注意的几点:

                要分为建条件边虚边(瞎取的名字)

                条件边不多说,就是\large B_j-B_i\geqslant ak建立\large B_i\large B_j长度为\large ak的边

	for(int i=1;i<=m;i++){
		scanf("%d%d%d",&b,&e,&t);
		add(b,e+1,t);
	}

                然而虚边就是,将1——N连接成一个链,边长取决于题目描述

                例如种树就是这样建的:

for(int i=0;i<=n;i++){
		add(i+1,i,-1);
		add(i,i+1,0);
}

                特别注意:跑最长路的时候,得用spfa因为有可能出现负数边

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值