[ARC142D]Deterministic Placing

Deterministic Placing

题解

场上救命题,本来前面罚时一大堆,幸好最后10分钟把这道题A了。

首先我们的把这道题题意读懂,就是说我们在树上的点上放一个黑点,每次操作要让所有黑点沿着一条边移动到相邻的点上,且没有两个黑点经过了同一条边或者再同一个点上。
最后求出所有能够操作无限次且操作后放有黑点的点集唯一的起始黑点集合数量。

可以发现,我们的黑点在这无限次操作中一定是会不断在一条边上反复横跳的,比如说这样 u → v → u → v → . . . u\rightarrow v\rightarrow u\rightarrow v\rightarrow ... uvuv...
那么一种显而易见的想法就是规划它是怎么反复很跳的。
由于每个点上操作后都只能留存一个点,所以我们把所有有点反复横跳的边拿出来一定是若干条不想交的链。

那么一种考虑就是去计算链的划分,显然,我们的链必然是有一个在顶端的白点与很多黑点构成,链的划分也很好 d p dp dp,自己记录当前链选了多少个点即可。
但这真的能够保证转移集合的唯一吗?好像不太能,同一种点的摆放实际上是可以对应多种链的划分的,而我们的要求是链的划分同时也是惟一的。
注意到一条链必定是顶端一个白点,然后一条链的黑点,于是我们就有了一个非常神奇的转化方法。
我们可以尝试着把这样的一条链看成一只箭" ⟶ \longrightarrow ",其中箭头是白点,箭身与箭尾都是黑点。
我们划分唯一的要求相当于我们这些箭身和箭尾不能接到别的其它的箭尾上面。
每次操作相当于把整个箭换个方向,原来的箭尾成了箭头,原来的箭头成了箭尾。
那么所有的要求就很明了了,划分出来后,箭身不能与任何箭头或者箭尾相接,否则就可以从箭身这里分开,将整个箭尾都接到相邻的箭尾上。
同样,两个箭尾不能相邻,两个箭头也不能相邻,否则一段箭尾就能取下来,接到另一个箭尾上。
状态设计就可以根据钦定这个点是箭头还是箭尾,箭身来确定。
定义 d p i , 0 / 1 / 2 , 0 / 1 , 0 / 1 dp_{i,0/1/2,0/1,0/1} dpi,0/1/2,0/1,0/1表示这个这个点是箭头,箭身,还是箭尾,如果是箭身或者箭尾前面接好没有,如果是箭身或者箭头后面接好没有。
转移也非常明确了,只有上面的情况不能成立,再把连接关系转移一下就行了。

时间复杂度 O ( n ) O\left(n\right) O(n),有个比较大的转移常数。

源码

然而场上我把所有状态都扔一起了,直接手写了一个 20 20 20行的 d p dp dp转移。

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef pair<LL,LL> pii;
#define MAXN 200005
#define pb push_back
#define mkpr make_pair
#define fir first
#define sec second
#define lowbit(x) (x&-x)
const int mo=998244353;
const int inv2=5e8+4;
const int jzm=2333;
const int zero=200000;
const int INF=0x3f3f3f3f;
const double LOG310=log(10)/log(3);
const double Pi=acos(-1.0);
const double eps=1e-9;
const int orG=3,ivG=332748118;
const int lm=10;
template<typename _T>
_T Fabs(_T x){return x<0?-x:x;}
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;
}
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 n,head[MAXN],tot,dp[MAXN][8],tmp[8];
struct edge{int to,nxt;}e[MAXN<<1];
void addEdge(int u,int v){e[++tot]=(edge){v,head[u]};head[u]=tot;}
void dosaka(int u,int fa){
    int num=0;dp[u][0]=dp[u][4]=dp[u][6]=1;
    for(int i=head[u];i;i=e[i].nxt){
        int v=e[i].to;if(v==fa)continue;dosaka(v,u);
        for(int j=0;j<8;j++)tmp[j]=0;
        Add(tmp[1],1ll*dp[u][0]*dp[v][4]%mo,mo);
        Add(tmp[2],1ll*dp[u][0]*dp[v][6]%mo,mo);
        Add(tmp[2],1ll*dp[u][0]*dp[v][2]%mo,mo);
        Add(tmp[3],1ll*dp[u][2]*dp[v][4]%mo,mo);
        Add(tmp[3],1ll*dp[u][1]*dp[v][6]%mo,mo);
        Add(tmp[1],1ll*dp[u][0]*dp[v][1]%mo,mo);
        Add(tmp[3],1ll*dp[u][1]*dp[v][2]%mo,mo);
        Add(tmp[3],1ll*dp[u][2]*dp[v][1]%mo,mo);
        Add(tmp[0],1ll*dp[u][0]*dp[v][3]%mo,mo);
        Add(tmp[1],1ll*dp[u][1]*dp[v][3]%mo,mo);
        Add(tmp[2],1ll*dp[u][2]*dp[v][3]%mo,mo);
        Add(tmp[3],1ll*dp[u][3]*dp[v][3]%mo,mo);
        Add(tmp[4],1ll*dp[u][4]*dp[v][7]%mo,mo);
        Add(tmp[5],1ll*dp[u][5]*dp[v][7]%mo,mo);
        Add(tmp[6],1ll*dp[u][6]*dp[v][5]%mo,mo);
        Add(tmp[7],1ll*dp[u][7]*dp[v][5]%mo,mo);
        Add(tmp[5],1ll*dp[u][4]*dp[v][2]%mo,mo);
        Add(tmp[5],1ll*dp[u][4]*dp[v][6]%mo,mo);
        Add(tmp[7],1ll*dp[u][6]*dp[v][1]%mo,mo);
        Add(tmp[7],1ll*dp[u][6]*dp[v][4]%mo,mo);
        for(int j=0;j<8;j++)dp[u][j]=tmp[j],tmp[j]=0;
    }
}
int main(){
    read(n);
    for(int i=1;i<n;i++){
        int u,v;read(u);read(v);
        addEdge(u,v);addEdge(v,u);
    }
    dosaka(1,0);
    printf("%lld\n",(1ll*dp[1][3]+dp[1][5]+dp[1][7])%mo);
    return 0;
}

谢谢!!!

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值