[AGC058C]Planar Tree

Planar Tree

题解

首先,我们看这题呀,感觉这东西好像很阴间的样子,想想有没有什么比较好的方法。
好像没有的样子。
首先当然可以有比较简单的 O ( n 3 ) O\left(n^3\right) O(n3)的区间 d p dp dp做法,不过这东西好像根本没有利用上我们 A i ∈ [ 1 , 4 ] A_i\in[1,4] Ai[1,4]的性质。
不妨考虑一下这个性质有什么用。

显然,这里我们可以发现, A i = 1 / 4 A_i=1/4 Ai=1/4的点连接到的点的权值都是唯一的,它们如果与 2 / 3 2/3 2/3相邻,显然可以直接合并到那上面。
同样,如果有两个相邻的权值相同的数,也是可以合并到一起的,因为它们必然可以连向相同的目标。
我们可以把上面两类点给合并起来,得到一个相邻的点都不同并且不存在 ( 1 , 2 ) / ( 3 , 4 ) (1,2)/(3,4) (1,2)/(3,4)相邻的图形。

但这又有什么用呢?
我们可以发现,我们的树其实是可以由不断合并在环上相邻的边构成的。
显然,对于一个大小为 n n n的环,其生成树上肯定会存在一个叶子与其父亲相邻,否则必然会出现相交的线段。
也就是说,我们可以由这种方式递归构造出整棵树。
我们上面的合并显然是优秀的合并,也就是说,它并不会影响最后的答案是否有解。
那么我们再来看在我们的新环上有能怎么办了?
如果这个环仅存在 2 2 2 3 3 3的时候显然是有解的,所以我们的目的是将这个环上的 1 , 4 1,4 1,4全部去掉。
如果要去掉 1 1 1,环上肯定得存在 2 2 2,为了两者不相邻,它们肯定会用 3 3 3隔开,也是说能合并的地方必然长这个样子: . . . , 1 , 3 , 1 , 3 , 1 , 3 , 1 , 3 , 2 , . . . ...,1,3,1,3,1,3,1,3,2,... ...,1,3,1,3,1,3,1,3,2,...,这样我合并 ( 1 , 2 ) (1,2) (1,2)后必然会去掉一个 3 3 3
同样,在合并 ( 3 , 4 ) (3,4) (3,4)后也必然去掉一个 2 2 2
也就是说,我们有解的必要条件是 c n t 2 ⩾ c n t 4 ∧ c n t 3 ⩾ c n t 1 cnt_2\geqslant cnt_4\wedge cnt_3\geqslant cnt_1 cnt2cnt4cnt3cnt1
但显然,它们两个不能同时取等,因为我们最后一次合并后肯定还会剩一个。
所以,我们真正的必要条件是: ( c n t 2 ⩾ c n t 4 ∧ c n t 3 > c n t 1 ) ∨ ( c n t 2 > c n t 4 ∧ c n t 3 ⩾ c n t 1 ) (cnt_2\geqslant cnt_4\wedge cnt_3>cnt_1)\vee(cnt_2>cnt_4\wedge cnt_3\geqslant cnt_1) (cnt2cnt4cnt3>cnt1)(cnt2>cnt4cnt3cnt1)
容易发现这个条件也是充分的,因为按照我们上面的方法是可以构造得到解的。

所以,只需要按我们上面的方法缩点后判断一下即可。
时间复杂度 O ( ∑ n ) O\left(\sum n\right) O(n)

源码

#pragma GCC optimize(2)
#pragma GCC optimize(3)
#pragma GCC optimize("Ofast")
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef pair<int,int> pii;
#define MAXN 300005
#define pb push_back
#define mkpr make_pair
#define fir first
#define sec second
#define lson (rt<<1)
#define rson (rt<<1|1)
const int INF=0x3f3f3f3f;
const int mo=998244353;
template<typename _T>
void read(_T &x){
    _T f=1;x=0;char s=getchar();
    while(s<'0'||s>'9'){if(s=='-')f=-1;s=getchar();}
    while('0'<=s&&s<='9'){x=(x<<3)+(x<<1)+(s^48);s=getchar();}
    x*=f;
}
template<typename _T>
_T Fabs(_T x){return x<0?-x:x;}
int add(int x,int y,int p){return x+y<p?x+y:x+y-p;}
void Add(int &x,int y,int p){x=add(x,y,p);}
int qkpow(int a,int s,int p){int t=1;while(s){if(s&1)t=1ll*a*t%p;a=1ll*a*a%p;s>>=1;}return t;}
int T,n,a[MAXN],b[MAXN],head,tail,cnt[5];
int main(){
    read(T);
    while(T--){
        read(n);head=tail=0;
        for(int i=1;i<=n;i++){
            read(a[i]);a[i]--;bool flag=0;
            while(head<tail){
                if(b[tail-1]==a[i]){tail--;continue;}
                if((b[tail-1]^a[i])==1){
                    if(a[i]==0||a[i]==3)flag=1;
                    else{tail--;continue;}
                }
                break;
            }
            if(!flag)b[tail++]=a[i];
        }
        while(head+1<tail){
            if(b[head]==b[tail-1])tail--;
            else if((b[head]^b[tail-1])==1){
                if(b[head]==0||b[head]==3)head++;
                else tail--;
            }
            else break;
        }
        for(int i=head;i<tail;i++)cnt[b[i]]++;
        if(cnt[0]>cnt[2]||cnt[3]>cnt[1])puts("No");
        else if(cnt[0]==cnt[2]&&cnt[3]==cnt[1])puts("No");
        else puts("Yes");
        cnt[0]=cnt[1]=cnt[2]=cnt[3]=0;
    }
    return 0;
}

谢谢!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值