题解 [AGC028D] Chords
挺妙的一道区间 dp。
首先把圆转化成序列。
把连通块的个数和转化成每个集合是一个连通块的方案数,求个和就是答案。
我们考虑怎么来刻画一个连通块。
注意到每一个连通块都可以唯一的对应到一段区间,左端点和右端点分别为连通块的最大值,最小值,连通块的所有元素全部在这个区间内(但区间内可能还有别的小连通块)。
于是我们可以计算这样的区间个数,他的左端点和右端点分别为连通块的最小、最大值。
可以发现的是区间内不会有向区间外的连边,不然区间外一定还会有和 l,r 连通的元素。
于是我们可以进行区间 dp。设 fl,r 表示 l,r 在一个连通块内,且为最小、最大值的方案数。
先排除掉不合法的区间,就是区间内未配对的数只有奇数个,或者区间有向区间外的连边。
考虑正难则反,用总数减去不合法的方案数。
设区间内未配对的数的个数为 cnt 。
总数就是 (cnt−1)!! ,双阶乘。
计算不合法的方案,也就是 l,r 不连通,我们枚举 l 所在的连通块,设为 l,i,那么方案数为 fl,i×gi+1,r
gl,r 表示的是 l,r 内随意配对的方案数,也就是上面的总数
直接区间 dp 即可,复杂度 O(n3)
Code
#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 这样的最小值不是 4 是 1 我才反应过来。。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】