agc020 vp记录

a,b是签到题。


一个集合由N个整数组成,请求出它的非空子集和的中位数。(\(N<=2000\) \(A_i<=2000\)

 
发现所有的子集和是关于所有数的和对称的。即有 \(X\) 则有 \(\sum{A_i} - X\),于是通过背包优化的bitset算出所有能拼出的数,从 $ \frac{\sum{A_i}}{2}$ 从小往大选第一个即可。


多组询问。每个询问给定四个整数,\(A,B,C,D\),求一个满足这个条件的字符串:

  1. 长度为\(A+B\),由\(A\)个字符A和·\(B\)个字符B构成。
  2. 在此基础上,连续的相同字符个数的最大值最小
  3. 在此基础上,字典序最小

输出这个字符串的第\(C\)位到第\(D\)位。

 
一个简单的分类讨论。题解讲的较清晰。没啥价值。


  • [AGC020E] Encoding Subsets
    我们定义一个 01 串的压缩是满足如下方式的字符串变化过程:

  • \(0\rightarrow 0,1\rightarrow 1\)

  • 如果 \(A\rightarrow P,B\rightarrow Q\) 合法,那么 \(A+B\rightarrow P+Q\) 也合法(其中 \(+\) 代表字符串拼接)

  • 如果 \(S=\underbrace{A+A+\cdots+A}_{n\text{个}(n\ge 2)}\),那么 \(S\rightarrow(A\times n)\) 也合法(其中 (, ), × 为字符,\(n\) 为数字,算作一个字符,即使其中有 \(0/1\)

我们同时定义 \(01\)\(B\)\(A\) 的子集当且仅当:

  • \(|A|=|B|\)
  • \(\forall B_i=1,A_i=1\)

现在给你一个 \(01\)\(S\),问它所有的子集的合法变化结果数的总和为多少。

答案对 \(998244353\) 取模。
 
观察到括号是可以重复嵌套的,于是从左向右的线性DP不太可行,考虑区间DP。
\(f_{i,j}\)为 i 到 j的方案数,\(g_{i,j}\)为将 i 到 j 划分进一个括号的方案数。
(不重不漏地统计括号匹配方案数,这是一个经典套路)
那么 \(f_{i,j}=\sum_{i\leq k\leq j} g_{i,k}\times f_{j+1,k}\)
记忆化搜索。

代码在这里
#include<bits/stdc++.h>
using namespace std;
inline int rd(){
	int f=1,j=0;
	char w=getchar();
	while(!isdigit(w)){
		if(w=='-')f=-1;
		w=getchar();
	}
	while(isdigit(w)){
		j=j*10+w-'0';
		w=getchar();
	}
	return f*j;
}
const int mod=998244353;
inline void ad(int &x,int y){return x+=y,x=(x>=mod)?x-mod:x,void();}
inline int mu(int x,int y){return 1ll*x*y%mod;}
unordered_map<string,int>f,g;
string I;
int getf(string s);
int getg(string s);
int getf(string s){
	if(s=="")return 1;
	if(f.count(s))return f[s];
	int len=s.length(),ansn=0;
	for(int i=1;i<=len;i++)ad(ansn,mu(getg(s.substr(0,i)),getf(s.substr(i,len-i+1))));
	return f[s]=ansn;
}
int getg(string s){
	if(s=="")return 1;
	if(s=="0")return 1;
	if(s=="1")return 2;
	if(g.count(s))return g[s];
	int ansn=0,len=s.length();
	for(int d=1;d<len;d++){
		if(len%d!=0)continue;
		string t="";
		for(int i=0;i<d;i++){
			bool flg=true;
			for(int j=i;j<len;j+=d)flg&=s[j]-'0';
			t=t+(char)('0'+flg);
		}
		ad(ansn,getf(t));
	} 
	return g[s]=ansn;
}
signed main(){
	cin>>I;
	printf("%lld\n",getf(I));
	return 0;
}

你有一个长度为\(C\)的圆,你在上面画了\(N\)个弧。弧\(i\)有长度\(l_i\)

每一条弧\(i\)随机均匀地放置在圆上:选择圆上的一个随机实点,然后出现一条以该点为中心的长度为\(l_i\)的弧。弧是独立放置的。例如,它们可以相互交叉或包含。

现在问你圆的每一个实点被至少一个弧覆盖的概率是多少?注意一条弧覆盖了它的两个端点。
(C<=50 N<=6)
 
众所周知,数据范围越小,题目越神秘。
此题首先是一个环DP,断环为链,将最长的线段的左端点作为起点。(这样不会出现某一条线段越过断点覆盖未被覆盖的环的情况)
对于求概率的DP,如果有确定点当然可以直接通过确定点DP,对于没有确定点的情况,因为概率和长度无关而之和长度或位置的相对关系有关,于是可以钦定一个相对大小关系来DP。
在此题上,我们可以钦定每条线段的小数部分的大小关系,并将每一段‘1’拆分为n段,小数排名为 i 的数只能放在每段‘1’中的第 i 个位置。
于是可以枚举大小关系(复杂度 \(n!\) ),每一种情况正常地做DP。
时间复杂度 \(2^n \times n! \times (n\times c)^2\)

代码在这里
#include<bits/stdc++.h>
using namespace std;
inline int rd(){
	int f=1,j=0;
	char w=getchar();
	while(!isdigit(w)){
		if(w=='-')f=-1;
		w=getchar();
	}
	while(isdigit(w)){
		j=j*10+w-'0';
		w=getchar();
	}
	return f*j;
}
int n,c,l[60];
double f[510][510],ans,cnt;
signed main(){
	n=rd(),c=rd();
	for(int i=0;i<n;i++)l[i]=rd();
	sort(l,l+n);
	while(true){
		for(int i=0;i<=n*c;i++)for(int s=0;s<(1<<n-1);s++)f[i][s]=0;
		f[l[n-1]*n][0]=1;
		for(int i=1,p;i<=n*c;i++){
			if((p=i%n-1)<0)continue;
			for(int j=i;j<=n*c;j++){
				for(int s=0;s<(1<<n-1);s++)if(~s>>p&1)f[min(c*n,max(j,i+l[p]*n))][s|(1<<p)]+=f[j][s];
			}
		}
		ans+=f[c*n][(1<<n-1)-1],cnt++;
		if(!next_permutation(l,l+n-1))break;
	}
	printf("%.13lf\n",ans/cnt/pow(c,n-1));
	return 0;
}

你以为这就结束了吗?这并不是最优复杂度!
有另一种思路,对于每一段1,如果我们不钦定相对大小关系而是直接DP,这样是会有精度问题的。然而如果对每一段‘1’划分得越多精度就越高。令分段数为 \(m\),有一个结论:答案关于分段数 m 是一个 n 次多项式。因为线段总数才 n 条,次数不可能更高(可以感性理解一下)。于是我们只需要 n+1 个点叉出多项式,把 m 等于 c, 2c ,···,(n+1)c带入求值即可。
最终的答案是:

\[ans= \lim_{m\to \infty}\frac{f(m)}{m^n} \]

上面这东西就是第 n 项的系数。
总复杂度是 \(2^n n^4 L^4\)

posted @ 2023-04-24 13:55  flywatre  阅读(27)  评论(0)    收藏  举报