便(then) (并查集)

9.23

思路:
考察并查集的应用.
第1个测试点:只有1行,无法形成2*2的区域,只要输入的数字中没有负数就一定有解.接下来我们默认已经排除了输入的数字有负数的情况.
第2,3个测试点:3^12枚举所有可能的情况.
第4,5个测试点:仔细观察一下性质.(1,1)+(2,2)=(1,2)+(2,1),实际上是(1,1)-(2,1)=(1,2)-(2,2).
也就是说:对于任意一列,两行之间的差相等.
如果不存在填满了数字的一列:这个差我们可以随便确定,比如说让两行之间的差为0.这样一定有解.
如果存在填满了数字的一列,我们就可以确定这个差.然后去填充可以填充的数字,只要没有矛盾,不会推导出负数,就一定有解.
如果有两列都填满但是得到的两行的差不相等,则无解.
第6,7个测试点:3行的情况比2行的情况复杂,如果手动讨论不同行之间的情况,似乎是比较麻烦的,可能会有同学写出来.
第8,9个测试点:坐标范围较小,但我没有想到对应的方法,可能会有选手想到针对性的算法.
第10个测试点(标算):
“对于任意一列,两行之间的差相等”是一个很重要的性质.这告诉我们:每一列差分后得到的结果相同.每一行差分后的结果也相同.于是我们对行列分别用带权并查集维护行之间,列之间的差分关系.如果差分关系出现矛盾(两行之间的差值可以推导出两种可能)则无解.如果差分关系没有矛盾,则需要求出整个矩阵中能推导出的最小值判断是否小于0.这里的推导方法是:维护差分关系的并查集必然分成了多个连通块.一种直观的方法是,枚举一个关于行的连通块,再枚举一个关于列的连通块,这两个连通块相交产生的格子中的最小值位于列和行的最小值的交点.这样的时间复杂度是很高的.实际上我们只需考虑每个关于列的连通块.对于一个关于列的连通块,我们求出这个连通块中数值最小的一列.考虑这个连通块中所有的已知数字,每个已知数字都可以求出数值最小的一列中某一个数值.在这些数值中取最小值即可,实现的时候,只需用每个已知数字求出这个连通块中某一列的最小值,再从这一列的最小值转换到最小的一列的最小值.

PS.数据有毒

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define GG  {puts("No");continue;}
const int maxn=100005;
typedef long long ll;
int ufs1[maxn];ll w1[maxn];
int ufs2[maxn];ll w2[maxn];
struct node{
  int x,y,val;
  void read(){
    scanf("%d%d%d",&x,&y,&val);
  }
}P[maxn];
bool cmpx(const node &A,const node &B){
  return A.x<B.x;
}
bool cmpy(const node &A,const node &B){
  return A.y<B.y;
}
int find1(int x){
  if(x==ufs1[x])return x;
  int rt=find1(ufs1[x]);
  w1[x]+=w1[ufs1[x]];
  return ufs1[x]=rt;
}
int find2(int x){
  if(x==ufs2[x])return x;
  int rt=find2(ufs2[x]);
  w2[x]+=w2[ufs2[x]];
  return ufs2[x]=rt;
}
bool link1(int a,int b,ll w){
  if(find1(a)!=find1(b)){
    int ra=find1(a),rb=find1(b);
    ufs1[ra]=ufs1[rb];
    w1[ra]=w+w1[b]-w1[a];
    return true;
  }else{
    return w1[a]==w+w1[b];
  }
}
bool link2(int a,int b,ll w){
  if(find2(a)!=find2(b)){
    int ra=find2(a),rb=find2(b);
    ufs2[ra]=ufs2[rb];
    w2[ra]=w+w2[b]-w2[a];
    return true;
  }else{
    return w2[a]==w+w2[b];
  }
}
ll Min1[maxn],Min2[maxn];
int main(){
  freopen("then.in","r",stdin);
  freopen("then.out","w",stdout);
  int tests;scanf("%d",&tests);
  while(tests--){
    bool flag=true;
    int R,C;scanf("%d%d",&R,&C);
    for(int i=1;i<=R;++i){
      ufs1[i]=i;w1[i]=0;
    }
    for(int i=1;i<=C;++i){
      ufs2[i]=i;w2[i]=0;
    }
    int n;scanf("%d",&n);
    for(int i=1;i<=n;++i)P[i].read();
    for(int i=1;i<=n;++i)if(P[i].val<0)flag=false;
    sort(P+1,P+n+1,cmpx);
    for(int i=1;i<n;++i)
      if(P[i].x==P[i+1].x)
    if(!link2(P[i].y,P[i+1].y,P[i+1].val-P[i].val))flag=false;

    sort(P+1,P+n+1,cmpy);
    for(int i=1;i<n;++i)
      if(P[i].y==P[i+1].y)
    if(!link1(P[i].x,P[i+1].x,P[i+1].val-P[i].val))flag=false;

    memset(Min1,0x3f,sizeof(Min1));
    memset(Min2,0x3f,sizeof(Min2));
    for(int i=1;i<=n;++i){
      int rt=find1(P[i].x);
      Min1[rt]=min(Min1[rt],P[i].val+w1[P[i].x]);
    }
    for(int i=1;i<=R;++i){
      int rt=find1(i);
      Min2[rt]=min(Min2[rt],-w1[i]);
    }
    for(int i=1;i<=R;++i)
      if(ufs1[i]==i&&Min1[i]+Min2[i]<0)
    flag=false;

    // memset(Min1,0x3f,sizeof(Min1));
    // memset(Min2,0x3f,sizeof(Min2));
    // for(int i=1;i<=n;++i){
    //   int rt=find2(P[i].y);
    //   Min1[rt]=min(Min1[rt],P[i].val+w2[P[i].y]);
    // }
    // for(int i=1;i<=C;++i){
    //   int rt=find2(i);
    //   Min2[rt]=min(Min2[rt],-w2[i]);
    // }
    // for(int i=1;i<=C;++i)
    //   if(ufs2[i]==i&&Min1[i]+Min2[i]<0)
    //  flag=false;

    printf("%s\n",flag?"Yes":"No");
  }
  return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值