“事物的矛盾法则,即对立统一的法则,是唯物辩证法的最根本的法则。”
-《矛盾论》
矛盾,它反映了事物之间的相互作用、相互影响的一种特殊状态。
本文讲述了两个信息之间怎样引发矛盾,利用并查集或者前缀等方法,快速找出矛盾。
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();
}
}