题解 [AGC028D] Chords

link

挺妙的一道区间 dp。

首先把圆转化成序列。

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

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

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

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

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

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

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

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

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

总数就是 \((cnt-1)!!\)双阶乘

计算不合法的方案,也就是 \(l,r\) 不连通,我们枚举 \(l\) 所在的连通块,设为 \(l,i\),那么方案数为 \(f_{l,i}\times g_{i+1,r}\)

\(g_{l,r}\) 表示的是 \(l,r\) 内随意配对的方案数,也就是上面的总数

直接区间 dp 即可,复杂度 \(O(n^3)\)

\(\sf{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\) 我才反应过来。。

posted @ 2022-05-04 18:09  CelticOIer  阅读(27)  评论(0编辑  收藏  举报