恩偶爱皮模拟尸体-30

水博客太快乐了

RT

考场

先把题都看了一下。。。
感觉只会暴力。。。。

回头看 \(T1\) 显然可以状压。。。
但是状压貌似会惨 \(T\) 。。。
于是考虑优化,这种状压的套路,显然可以大力 \(meet\ in\ the\ middle\) 。。。
于是就这么写了。。。感觉过了。。
写了三题的暴力, \(T1\) 开始对拍,发现数据水了,加强数据又找了几个 \(bug\) ,感觉 \(T1\) 绝对过了。。。

考虑优化 \(T2\) \(T3\) ,啥也没想出来。。。

临结束大概 \(5min\) 发现 \(T1\) 理解错题意了。。。
人没了。。。。

分数

\(t1\ 35pts\ +\ t2\ 20pts\ +\ t3\ 10pts\ =\ 65pts\)

搞不懂 \(T1\) 理解错题意为什么还有分。。。。
挂惨了。。。

题解

A. 毛一琛

发现以我的思路稍微改一改就可以了。。。
确实就是 \(meet\ in\ the\ middle\)。。。

考虑维护左边 \(n/2\) 的所有合法子集中的每种状态,以及这个自己划分为两个无交集的部分后的差值,一差值为下标,因为太大所以可以用 \(map\) 维护。。。
再枚举右边 \(n/2\) 的所有合法子集,将差值相同的两部分拼在一起,考场上写的是直接计数,题意多一个去重,直接用 \(map+vector\) 即可。。。

code
#include<bits/stdc++.h>
using namespace std;
const int N=25, M=3e6+10, mod=1e6+7;
inline int read(){
	int f=1, x=0; char ch=getchar();
	while(!isdigit(ch)) { if(ch=='-') f=-1; ch=getchar(); }
	while(isdigit(ch)) { x=x*10+ch-48; ch=getchar(); }
	return f*x;
}
int n, n1, n2, bas, ul;
int a[N], ans, co[M];
map<int, vector<int> > m;
bool vis[M];
int main(void){
	n=read(); n1=n>>1, n2=n-n1; bas=1<<n1;
	for(int i=1; i<=n; ++i) a[i]=read();
	for(int i=0; i<(1<<n1); ++i) for(int j=i, k=1; j; j>>=1, ++k)
		if(j&1) co[i]+=a[k];
	for(int i=0; i<(1<<n); i+=bas) for(int j=1<<n1, k=n1+1; j<1<<n; j<<=1, ++k)
		if(i&j) co[i]+=a[k];
	for(int i=0; i<(1<<n1); ++i) for(int j=0; j<(1<<n1); ++j)
		if((i&j)==0) m[co[i]-co[j]].push_back(i|j);
	for(int i=0; i<(1<<n); i+=bas) for(int j=0; j<(1<<n); j+=bas)
		if((i&j)==0) for(int v : m[co[i]-co[j]]) vis[v|i|j]=1;
	for(int i=1; i<1<<n; ++i) ans+=vis[i];
	printf("%d\n", ans);
	return 0;
}

B. 毛二琛

这题着实不讲武德。。。。

首先求出对于给出的序列,可以求出对于任意相邻的两位 \(i\)\(i+1\) ,必须先翻转第 \(i\) 位还是先翻转第 \(i+1\) 位,将其存在数组 \(cmp\) 中,用 \(cmp=1\) 表示必须先翻当前位再翻下一位,根据排列的性质, \(cmp\)\(1e10086\) 种求法,但是要注意一些细节。。。

求出 \(cmp\) 后进行 \(dp\) ,求出可以构造出多少种 \(q\) 序列即可。。
考虑设计状态,因为已知相邻两个点之间的顺序,不妨设 \(f_{i,j}\) 表示前 \(i\) 位中,数字 \(i\) 在序列 \(q\) 中的位置为 \(j\) 时的方案数。。。
转移很好像,再次不加以赘述。。。。
实际上是懒得写了,建议去看这位巨佬博客

code
#include<bits/stdc++.h>
using namespace std;
const int N=5010, mod=1e9+7;
inline int read(){
	int f=1, x=0; char ch=getchar();
	while(!isdigit(ch)) { if(ch=='-') f=-1; ch=getchar(); }
	while(isdigit(ch)) { x=x*10+ch-48; ch=getchar(); }
	return f*x;
}
int n, a[N];
bool vis[N];
int f[N][N];
void get_cmp(int l, int r){
	if(l>r) return;
	int maxn=0;
	for(int i=l; i<=r; ++i) if(a[i]>a[maxn]) maxn=i;
	for(int i=maxn; i<r; ++i) vis[i]=1;
	if((maxn==r&&a[r]>a[r+1])||a[maxn]>a[r+1]) vis[r]=1;
	vis[maxn-1]=0; get_cmp(l, maxn-2);
}
int main(void){
	n=read();
	for(int i=1; i<=n; ++i) a[read()+1]=i;
	get_cmp(1, n); f[1][1]=1;
	for(int i=2; i<n; ++i){
		for(int j=1; j<=n; ++j) f[i-1][j]+=f[i-1][j-1], f[i-1][j]%=mod;
		for(int j=1; j<=i; ++j){
			if(!vis[i-1]) f[i][j]=f[i-1][j-1];
			else f[i][j]=((f[i-1][i-1]-f[i-1][j-1])%mod+mod)%mod;
		}
	}
	for(int i=1; i<n; ++i) f[n-1][i]+=f[n-1][i-1], f[n-1][i]%=mod;
	printf("%d\n", f[n-1][n-1]);
	return 0;
}

C. 毛三琛

大力枚举 \(x\) ,对于每个 \(x\) 大力二分答案, \(check\) 直接贪心即可,要注意一个细节,若数列中出现一个数大于当前答案,则当前答案显然不可行。。。。

加一个玄学减枝,对于新枚举到的没一个 \(x\) ,再二分前先 \(check\) 一遍当前的最小答案,若不可行则当前 \(x\) 显然不会对答案有贡献。。。
很玄学,但是加上确实可以过。。。

code
#include<bits/stdc++.h>
using namespace std;
const int N=1e4+10, INF=1e9+7;
inline int read(){
	int f=1, x=0; char ch=getchar();
	while(!isdigit(ch)) { if(ch=='-') f=-1; ch=getchar(); }
	while(isdigit(ch)) { x=x*10+ch-48; ch=getchar(); }
	return f*x;
}
int n, p, k, a[N], ans=INF;
bool check(int x){
	int ul=0, num=0;
	for(int i=1; i<=n; ++i){
		if(x<a[i]) return 0;
		if(ul+a[i]>x) ul=a[i], ++num;
		else ul+=a[i];
	}
	if(ul) ++num;
	return num<=k;
}
int main(void){
	n=read(), p=read(), k=read();
	for(int i=1; i<=n; ++i) a[i]=read();
	for(int i=1; i<=p; ++i){
		for(int j=1; j<=n; ++j) ++a[j], a[j]%=p;
		if(!check(ans)) continue;
		int l=0, r=ans, mid;
		while(l<r){
			mid=(l+r)>>1;
			if(check(mid)) r=mid;
			else l=mid+1;
		}
		ans=l;
	}
	printf("%d\n", ans);
	return 0;
}

认真审题,小心爆零!!!

posted @ 2021-08-04 17:59  Cyber_Tree  阅读(46)  评论(0编辑  收藏  举报