题解 [AGC028D] Chords

link

挺妙的一道区间 dp。

首先把圆转化成序列。

把连通块的个数和转化成每个集合是一个连通块的方案数,求个和就是答案。

我们考虑怎么来刻画一个连通块。

注意到每一个连通块都可以唯一的对应到一段区间,左端点和右端点分别为连通块的最大值,最小值,连通块的所有元素全部在这个区间内(但区间内可能还有别的小连通块)。

于是我们可以计算这样的区间个数,他的左端点和右端点分别为连通块的最小、最大值。

可以发现的是区间内不会有向区间外的连边,不然区间外一定还会有和 l,r 连通的元素。

于是我们可以进行区间 dp。设 fl,r 表示 l,r 在一个连通块内,且为最小、最大值的方案数。

先排除掉不合法的区间,就是区间内未配对的数只有奇数个,或者区间有向区间外的连边。

考虑正难则反,用总数减去不合法的方案数。

设区间内未配对的数的个数为 cnt

总数就是 (cnt1)!!双阶乘

计算不合法的方案,也就是 l,r 不连通,我们枚举 l 所在的连通块,设为 l,i,那么方案数为 fl,i×gi+1,r

gl,r 表示的是 l,r 内随意配对的方案数,也就是上面的总数

直接区间 dp 即可,复杂度 O(n3)

Code

Copy
#include<bits/stdc++.h> #define N 2001001 #define MAX 2001 using namespace std; typedef long long ll; typedef double db; const ll mod=998244353,inf=1e18,inv2=(mod+1)/2; inline void read(ll &ret) { ret=0;char c=getchar();bool pd=false; while(!isdigit(c)){pd|=c=='-';c=getchar();} while(isdigit(c)){ret=(ret<<1)+(ret<<3)+(c&15);c=getchar();} ret=pd?-ret:ret; return; } ll n,a[N],ans,sum; bitset<50000*25>b[51][51]; ll ret[2][N]; signed main() { read(n); for(int i=1;i<=n*2;i++) read(a[i]); sort(a+1,a+n*2+1); ans=a[1]+a[2]; for(int i=3;i<=n*2;i++) sum+=a[i]; ret[0][1]=a[1],ret[1][n]=a[2]; b[2][0].set(0); for(int i=3;i<=n*2;i++) for(int j=n-1;j;j--) b[i][j]=b[i-1][j]|(b[i-1][j-1]<<a[i]); ll res=inf,p=0; for(int i=0;i<=50000*25;i++) if(b[n*2][n-1][i]) { if(max(ll(i),sum-i)<res) { res=max(ll(i),sum-i); p=i; } } ll tot=n-1; ll c=0; for(int i=n*2;i>=3;i--) { if(!tot) { ret[1][++c]=a[i]; continue; } if(b[i-1][tot][p]) ret[1][++c]=a[i]; else ret[0][(tot--)+1]=a[i],p-=a[i]; } for(int i=0;i<=1;i++) { for(int j=1;j<=n;j++) printf("%lld ",ret[i][j]); putchar('\n'); } exit(0); }

坑:我原来以为 l>r 的区间也要做一遍,因为是个环要考虑两个方向,直到yhw告诉我,像 456781 这样的最小值不是 41 我才反应过来。。

posted @   CelticOIer  阅读(29)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示