20210804 noip30

考场

第一眼感觉 T1 是状压 DP,弃了。T2 好像也是 DP???看上去 T3 比较可做。
倒序开题。T3 暴力是 \(O(pn\log p)\)(枚举 \(x\),二分答案,看能否分成合法的不超过 \(k\) 组),先写个随机化,和暴力拍了一下发现正确率不太行,退火收敛太慢,只能爬山了,结果还不如直接随机???打表发现峰值特别多。。。死了。强行让退火快速收敛+随机化卡时到 0.98s,拍了拍发现 \(n,p\le5000\) 的时候正确率还行。没注意时间,写完 8.30 了。
T2 完全不会做,直接暴力。手玩数据发现和逆序对有关,但还是只会判无解,后来写了个复杂度不明(也许是 \(O(ans)\))的做法,一分也没骗到。不会造数据,随机了一些发现都是无解。。。爬了
9.30 才开 T1,想了想 DP(根本就不是 DP 啊)就写暴力了。写了 \(O(2^nn\sum m)\) 的暴力,分 \(\sum m\) 的大小用 bitsetbool 做背包,发现有一个 \(n=17,m\le2\times10^4\) 的点不会做(其实是 \(O(3^n)\) 枚举每个数不要/放第一个集合/放第二个),一直想到结束。

res

rk14 30+20+50
T3 退火还是太慢,导致后面的点全 T 了。(不然能拿 60~70pts)

rk1 yzf 45+100+50
rk3 牛宏昊 90+20+40
rk5 ycx 75+20+50
rk6 wcr 80+0+60
rk7 刘荣信 15+20+100
rk9 zjj 35+20+70

总结

主要死在了 T1,如果不从 DP 的方向想拿到 70pts 及以上还是比较容易的,以后不要直接确定一个方向,先直接想想,想不出来再根据感觉想特定的算法。

乱搞能力还是太死板,这场的 T1 T3 数据都水,各种做法都能拿到高分甚至 A

毛一琛

meet in the middle

先爆算前 \(\frac n2\) 个数的状态,记下差值和并集。
再枚举后 \(\frac n2\) 个,对于每个差值查和前 \(\frac 2n\) 中和它相同的状态(最多 \(2^{\frac n2}\) 个),然后把两个状态的或计入答案。
时间复杂度 \(O(6^{\frac n2})\)。题解说调整前后的大小能更快,但实测 \(\frac n3,\frac{2n}3\) 都不如 \(\frac n2\)

我选择了用 map 离散化差值,vector 存每个差值的状态,bool 数组去重,貌似不是正解(跑得很慢)

code
const int N = 22;
int n,a[N];

int m,ind,ans;
bool vis[1<<N];
unordered_map<int,int> id;
vector<int> state[1<<N];

void dfs1(int u,int del,int s) {
	if( u > m ) {
		if( !id.count(del) ) id[del] = ++ind;
		state[id[del]].pb(s);
		return;
	}
	dfs1(u+1,del,s);
	dfs1(u+1,del+a[u],s|(1<<u-1));
	dfs1(u+1,del-a[u],s|(1<<u-1));
}
void dfs2(int u,int del,int s) {
	if( u > n ) {
		if( !id.count(del) ) return;
		int i = id[del];
		for(int t : state[i]) vis[s|t] = 1;
		return;
	}
	dfs2(u+1,del,s);
	dfs2(u+1,del+a[u],s|(1<<u-1));
	dfs2(u+1,del-a[u],s|(1<<u-1));
}

signed main() {
	read(n); m = n/2.0;
	For(i,1,n) read(a[i]);
	dfs1(1,0,0);
	dfs2(m+1,0,0);
	for(int i = 1, j = 1<<n; i < j; ++i) ans += vis[i];
	write(ans);
	return iocl();
}

毛二琛

下文将 \([0,n-1]\) 对应到 \([1,n]\)

首先如果有 \(i=p_i\) 那么无解,但数据中没有,所以不用判

问题可以转化为 \(n-1\) 个数排列,对 \(i,i+1\) 的前后顺序有要求(这点保证了每个排列对应一个答案),求方案数。
先处理前后顺序。设 \(cmp[i]=0\) 表示 \(i\)\(i+1\) 的前面,\(=1\) 表示在后面。若 \(i<p_i\),那么 \(cmp[i-1]=1\)(先要把数 \(p_i\)\(i\) 换到 \(i+1\) 才能换 \(i-1,i\)),\(cmp[i..p_i-2]=0\)(数 \(p_i\) 要从 \(i\) 一直向右换到 \(p_i\),期间换了左边的才能换右边,不然数 \(p_i\) 就无法继续向右了),\(cmp[p_i-1]=1\)(换 \(p_i-1,p_i\) 后数 \(p_i\) 一定在 \(p_i\) 上,此后换 \(p_i,p_i+1\) 会导致数 \(p_i\) 被换离);\(i>p_i\) 的情况同理。

考虑 DP。设 \(f[i,j]\) 为前 \(i\) 个数中 \(i\) 排在第 \(j\) 位的方案数,转移:

\[f[i,j]=\left\{\begin{matrix} \sum_{k=j}^{i-1}f[i-1,k],cmp[i-1]=1 \\ \sum_{k=1}^{j-1}f[i-1,k],cmp[i-1]=0 \end{matrix}\right. \]

\(cmp[i-1]=1\)\(k\in[j,i-1]\) 是因为在前 \(i\) 个数中 \(i-1\) 排在第 \(j\) 位之后,那么在前 \(i-1\) 个数中就要排在 \([j,i-1]\),可以看作将 \(i\) 插进前 \(i-1\) 个数排列的第 \(j\) 位,那么原来的 \(j..i-1\) 位就要向右移一位。第二维可以前缀和优化,时间复杂度 \(O(n^2)\)

code
const int N = 5e3+5, mod = 1e9+7;
int n,a[N];

int f[N][N],sum[N][N];
bool cmp[N];

signed main() {
	read(n);
	For(i,1,n) read(a[i]), ++a[i];
	for(int i = 1; i < n; ++i)
		if( i < a[i] ) cmp[i-1] = cmp[a[i]-1] = 1;
		else for(int j = a[i]; j < i-1; ++j) cmp[j] = 1;
	f[1][1] = sum[1][1] = 1;
	for(int i = 2; i < n; ++i) {
		if( cmp[i-1] ) For(j,1,i) f[i][j] = (sum[i-1][i-1]-sum[i-1][j-1]+mod)%mod;
		else For(j,1,i) f[i][j] = sum[i-1][j-1];
		For(j,1,n) sum[i][j] = (sum[i][j-1] + f[i][j]) %mod;
	}
	write(sum[n-1][n-1]);
	return iocl();
}

毛三琛

需要加一个优化:随机枚举 \(x\)\(O(n)\) 判断在当前答案下能否分成不超过 \(k\) 组,如果可以再去二分,时间复杂度 \(O(qn+n\log n\log q)\)

code
const int N = 1e4+5;
int n,p,m,a[N];

int ans,b[N],q[N];
mt19937 mt(time(0));

bool check(int x) {
	int res = 1, sum = 0;
	For(i,1,n) {
		if( b[i] > x ) return 0;
		if( sum+b[i] <= x ) sum += b[i];
		else ++res, sum = b[i];
	}
	return res <= m;
}
int calc(int x) {
	int l = 1, r = ans;
	while( l < r ) {
		int mid = l+r>>1;
		if( check(mid) )  r = mid;
		else l = mid+1;
	}
	return l;
}

signed main() {
	read(n,p,m);
	For(i,1,n) read(a[i]), ans += a[i];
	for(int i = 0; i < p; ++i) q[i] = i;
	shuffle(q,q+p,mt);
	for(int i = 0; i < p; ++i) {
		For(j,1,n) b[j] = (a[j] + q[i]) %p;
		if( check(ans) ) ans = calc(q[i]);
	}
	write(ans);
	return iocl();
}
posted @ 2021-08-04 15:16  401rk8  阅读(104)  评论(1编辑  收藏  举报