矛盾|程序设计

“事物的矛盾法则,即对立统一的法则,是唯物辩证法的最根本的法则。”

                                                                                -《矛盾论》

矛盾,它反映了事物之间的相互作用、相互影响的一种特殊状态。

本文讲述了两个信息之间怎样引发矛盾,利用并查集或者前缀等方法,快速找出矛盾。

1从认识上看,具有怎样特征是矛盾的?

2从实践上看,在程序上怎样会表现出矛盾。

3如果不是矛盾的,如何处理?

图片

矛盾的产生不可能只是一个事物,我们在算法竞赛中可能要判断是否是矛盾。

https://vjudge.net.cn/problem/HDU-3038

图片

给定区间[a,b],区间和为v。输出m组数据,每输入一组,判断一组条件是否和之前的矛盾,输出和前面矛盾的数据的个数。

我们知道加法实际上是具有传递性质的。

比如a+2=b,b+3=c

a+?=c

其中?=5

我们发现加法具有一种带权值的传递性,这里的2,3,?都是权值。

我们定义数组s为它所要参照的对象(可以是自己),d[x]为x比s[x]多了什么...

比如a+3=b,我们b是以a为参照对象的,我们可以绘制如下的图。

图片

如果b+2=c,那么我们又得到了

图片

是不是可以简化成什么

这样表现的关系也可以表示成这样。

图片

于是,我们可以使用路径压缩的带权并查集。

这里我们认为有前缀和的思想,比如1-5之间的和为500,

图片

图片

#include<iostream>#include<map>#include<cstring>using namespace std;const int N=200010;int s[N];int d[N];int ans;void init_set(){  for(int i=0;i<=N;i++)  {    s[i]=i;    d[i]=0;  }}int find_set(int x){  if(x!=s[x])  {    int t=s[x];//存储父节点。    s[x]=find_set(s[x]);    d[x]+=d[t];  }  return s[x];}void merge_set(int a,int b,int v){  int roota=find_set(a),rootb=find_set(b);  if(roota==rootb)//如果发现它们本身有关系。那么查找这个关系是否是v  {    if(d[a]-d[b]!=v)ans++;  }  else {    s[roota]=rootb;//    d[roota]=d[b]-d[a]+v;  }}signed main(){  int n,m;  while(cin>>n>>m)  {    init_set();    ans=0;    while(m--)    {      int a,b,v;      cin>>a>>b>>v;      a--;      merge_set(a,b,v);      }    printf("%d\n",ans);  }}

图片

https://www.luogu.com.cn/problem/P2024

图片

图片

图片

图片

s[x]表示的是根节点,d[x]表示和根节点的关系。

不同d值的维护我们通过找关系,通过取模合并关系。

#include<iostream>
#include<map>
#include<cstring>
using namespace std;
const int N=50010;
int s[N];
int d[N];
int ans;
void init_set()
{
	for(int i=0;i<N;i++)
	{
		s[i]=i;
		d[i]=0;
	}
	
}
int find_set(int x)
{
	if(x!=s[x])
	{
		int t=s[x];
		s[x]=find_set(s[x]);
		d[x]=(d[x]+d[t])%3;
	}
	return s[x];
}
void merge_set(int x,int y,int relation)
{
	int rootx=find_set(x);
	int rooty=find_set(y);
	if(rootx==rooty)
	{
		if((relation-1)!=(d[x]-d[y]+3)%3)
		ans++;
	}
	else 
	{
		s[rootx]=rooty;
		d[rootx]=(d[y]-d[x]+relation-1)%3;
	}
}
signed main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	int n,k;
	cin>>n>>k;
	init_set();
	ans=0;
	while(k--)
	{
		int relation;
		int x,y;
		cin>>relation>>x>>y;
		if(x>n||y>n||(relation==2&&x==y))ans++;
		else merge_set(x,y,relation);
	}
	cout<<ans;
}

图片

https://www.luogu.com.cn/problem/CF1675C

这道题考察的是逻辑的推断,

因为只有一个说谎,枚举某个人是否说谎,也就是假设这个人说谎,查找是否产生矛盾。

如果不产生矛盾,那么还真有可能是这个人,使得答案增加1。

如果产生矛盾,那么肯定不可能是这个人。

图片

图片

#include<iostream>#include<map>#include<cstring>using namespace std;string s;int T;void solve(){  cin>>s;  int ans=0;  for(int i=0;i<s.size();i++)  {    int flag=true;    for(int j=i-1;j>=0;j--)    {      if(s[j]=='0')flag=false;    }    for(int j=i+1;j<s.size();j++)    {      if(s[j]=='1')flag=false;    }    if(flag)ans++;  }  cout<<ans<<"\n";}signed main(){  cin>>T;  while(T--)  {    solve();  }}

但是这样会超时。

这个时候我们要用前缀/后缀记录的思想。

has0[i]表示从1-n,是否有0的产生。

has1[i]表示从i-n,是否有1的产生。

如果不是两端的,那么我们要保证has0[i-1]是false,并且has1[i+1]是false,自身无所谓,因为小偷可以随便说。

#include<iostream>
#include<map>
#include<cstring>
using namespace std;
const int N=2000100;
string s;
int T;
bool has1[N];
bool has0[N];
void solve()
{
	memset(has1,0,sizeof has1);
	memset(has0,0,sizeof has0);
	cin>>s;
	int ans=0;
	has0[0]=(s[0]=='0');
	has1[s.size()-1]=(s.back()=='1');
	for(int i=1;i<s.size();i++)
	{
		has0[i]=has0[i-1]|(s[i]=='0');
	}
	for(int i=s.size()-2;i>=0;i--)
	{
		has1[i]=has1[i+1]|(s[i]=='1');
	}
	for(int i=0;i<s.size();i++)
	{
		if(i==0)
		{
			if(!has1[i+1])
			ans++;
		}
		else if(i==s.size()-1)
		{
			if(!has0[i-1])
			{
				ans++;
			}
		}
		else {
			if((!has0[i-1])&&(!has1[i+1]))
			{
				ans++;
			}
		}
	}
	cout<<ans<<'\n';
}
signed main()
{
	cin>>T;
	while(T--)
	{
		solve();
	}
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值