【题解】CF1854 合集

CF1854A2 Dual (Hard Version)

你考虑我们 A1 只需要通过自加凑一个最大的数,然后将所有的数都变成正数,最后做一次前缀和即可。(不懂可以看看落谷题解)

好,我们现在去看 Hard Version\(31\) 次操作怎么分配:

  • 前缀和(全为正)/ 后缀和 (全为负)—— \(19\)

  • 还剩下 \(12\) 次,不知道该怎么做。

我们的目标便变为了:在 \(12\) 次之内让所有数变成全为正/全为负

现在,我们需要整理一下我们手中的方法:

  • 我们 Easy Version 中是怎么做的呢,我们找了一个数,然后让它变成极大/极小,这一步在最劣情况下显然是 \(5\) 次 (\(1\to2\to4\to8\to16\to32\)),然后所有数都要变一遍 —— \(5+n\)
  • 当然,我们也有一种方法就是说找一个绝对值最大的数,然后让它给所有数加一遍,让其他的数和它符号相同 —— \(n\)

当然上面的 \(n\) 其实是上限,准确来说应该将 \(n\) 替换为和选出的数异号的数的个数。

那么,我们就可以将两种方法结合一下。

  • 我们先找到一个绝对值最大的数 \(x\),然后计算和这个数符号相同的数的个数(包括 \(0\)
  • 如果同号的数的个数 \(\geq 7\)(异号的数的个数 \(\leq 12\)),那么就直接让这个这些数都加上 \(x\)\(\leq 12~times\)
  • 否则,就造一个和 \(x\) 异号的绝对值极大的数,然后将 \(x\) 和与 \(x\) 同号的数都加上这个数。(\(\leq 5 + 6 + 1~times\)

最后将序列变为原序列的前/后缀和序列即可.

code:

#include<bits/stdc++.h>
using namespace std;
const int NN = 40;
int t,n;
int a[NN];
int maxn,pos;
int main(){
	scanf("%d",&t);
	while(t--){
		scanf("%d",&n);
		maxn = 0,pos = 0;
		for(int i = 1; i <= n; ++i){
			scanf("%d",&a[i]);
			if(abs(a[i]) > maxn) maxn = abs(a[i]), pos = i;
		}
		if(maxn == 0){puts("0");continue;}
		int cnt = 0;
		for(int i = 1; i <= n; ++i){
			if(a[i] * a[pos] >= 0 && i != pos) ++cnt;
		}
		if(cnt == n-1){
			printf("%d\n",n-1);
		}
		else if(cnt < 7){
			printf("%d\n",5 + cnt + n);
			for(int i = 1; i <= n; ++i)
				if(a[i] * a[pos] < 0){pos = i;break;}
			for(int i = 1; i <= 5; ++i) printf("%d %d\n",pos,pos);
			for(int i = 1; i <= n; ++i)
				if(a[i] * a[pos] <= 0 && i != pos) printf("%d %d\n",i,pos);
		}
		else{
			printf("%d\n",n - cnt - 1 + n - 1);
			for(int i = 1; i <= n; ++i){
				if(a[i] * a[pos] < 0 && i != pos) printf("%d %d\n",i,pos);
			}
		}
		if(a[pos] > 0) for(int i = 1; i < n; ++i) printf("%d %d\n",i+1,i); 
		else for(int i = n; i > 1; --i) printf("%d %d\n",i-1,i);
	}
} 

CF1854B Earn or Unlock

标签:DP \(B\) | 杂项 \(C\)

你考虑,我们很容易地可以构造一个 \(n^2\) 状态的 DP

  • \(f_{i,j}\) 表示当前在 \(i\) 张牌,还可以摸 \(j\) 张牌的最大分数。转移也很好转移,你考虑一眼就会。

但是我们显然要缩减复杂度,我们看到数据范围 \(10^5\),想到了根号。

分块???显然不行。莫队???都没有区间查询,怎么行呢?

然后你苦思冥想,到最后也没有想出来这道题。

你考虑其实我们可以把 \(j\) 这一维吃掉,如果我们将继续往下摸牌少去的牌的代价均摊到后面的 \(a_i\) 张牌上,那么我们知道一个 \(i\),即可求出在该点的分数。

即对于位置 \(i\),对应的分数为 \(\sum\limits_{j = 1}^n a_j - i + 1\)

然后现在我们的要求就变成了看到底能不能在位置 \(i\) 摸到完最后一张牌。

我们可以发现这个转移是 \(n^2\) 的,但是因为状态只有 \(0/1\) 所以说可以使用 bitset 进行优化,让复杂度变为 \(O(\frac {n^2} \omega)\)

处理的时候还有一个细节,就是计算的时候,摸牌的最后一个位置可能会到达 \(2\times n\),我们只需要将 \(a_{n+1\sim2n}\) 都赋值为 \(0\) 即可。

code:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll NN = 2e5 + 8;
ll n,m,ans;
ll a[NN],pre[NN];
bool f[NN];
bitset<NN> dp;
int main(){
	scanf("%lld",&n);
	pre[0] = 0;dp[1] = 1;
	for(int i = 1; i <= n; ++i)
		scanf("%lld",&a[i]), pre[i] = pre[i-1] + a[i];
	for(int i = 1; i <= n; ++i){
		dp = (dp | (dp << a[i]));
		f[i] = dp[i];dp[i] = 0;
	}
	for(int i = n + 1; i <= 2 * n; ++i) {
		f[i] = dp[i];
		pre[i] = pre[i - 1];
	}
	ans = 0;
	for(int i = 1; i <= 2 * n; ++i) if(f[i]) ans = max(ans, pre[i] - i + 1);
	printf("%lld", ans);
	return 0;
}

CF1854C Expected Destruction

标签:DP \(B^+\) | 数学 \(C^+\)

你考虑,我们如果没有重合就将元素删去的操作,我们就有答案:\(n \times (m+1) - \sum\limits_{i=1}^n a_i\)

但是,我们显然最后的答案是小于这个的,如果有两个数在 \(i\) 相撞,那么我们的答案就会减少 \((m-i+1)\)

我们设 \(f_{i,j}\) 表示两个数分别在 \(i\)\(j\) 的概率 \((i\leq j)\)\(f_{i,i}\) 表示第一次相撞在 \(i\) 的概率,我们可以得到下面递推式:

\[f_{i,j} = f_{i,j-1} \times \frac {[i \neq j-1]} 2 + f_{i-1,j} \times \frac {[i-1 \neq j]} 2 \]

然后我们最终的答案即为:

\[n \times (m+1) - \sum\limits_{i=1}^n (a_i + f_{i,i} \times (m-i+1)) \]

code:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int NN = 508,MOD = 1e9 + 7;
bool Med; 
int n,m;
ll ans;
ll s[NN];
ll f[NN][NN];
bool Mbe;
int main(){
	scanf("%d%d",&n,&m);
	for(int i = 1; i <= n; ++i) {
		scanf("%lld",&s[i]);
		ans = (ans + m + 1 - s[i]) % MOD;
		if(i != 1) f[s[i-1]][s[i]] = 1;
	}
	for(int i = 1; i <= m; ++i){
		ans = (ans + MOD - 1ll * f[i][i] * (m - i + 1) % MOD) % MOD;
		for(int j = i + 1; j <= m; ++j){
			f[i][j+1] = (f[i][j+1] + f[i][j] * (MOD+1 >> 1) % MOD) % MOD;
			f[i+1][j] = (f[i+1][j] + f[i][j] * (MOD+1 >> 1) % MOD) % MOD;
		}
	}
	printf("%lld\n",ans);
//	fprintf(stderr, "%.3lf MB\n", (&Mbe - &Med) / 1048576.0);
//	fprintf(stderr, "%.5lf ms\n", 1e3 * clock() / CLOCKS_PER_SEC);
}

CF1854D Michael and Hotel

标签:思维题 \(B^+\)

交互题。

考虑题意即为找到 \(1\) 所在内向基环树上的所有点。

我们考虑我们怎么找到环上的点,我们考虑我们可以 \(O(\log n)\) 询问到一个环上的点,方法即为将 \(k\) 定为一个大数,然后二分点集。然后我们便可以在 \(O(n\log n)\) 的时间复杂度内找到所有环上的点(我们一会儿再讲怎样优化)。

我们找到环上的点了之后,我们再将 \(S\) 设为环上的点,\(k\) 设为一个极大数,\(u\)\(1\sim n\) 遍历,如果返回 \(1\) 即为可以到达,否则不能到达,这样我们就可以在 \(O(n)\) 的时间内找到所有链上的点。

我们显然现在需要将找环上的点的操作再精简一下。

我们考虑我们找链上的点时的方法十分地高效,我们可以略作修改来找环。

我们考虑假设我们找到了环上的长度为 \(len\) 的链,那么我们可以将 \(S\) 设为已经找到的环上的点,\(k = len\)\(u\)\(1\sim n\) 遍历。返回 \(1\) 便将点加入环,这样,我们每次的环长就可以倍增了,停止条件就是环长没有倍增(即绕了一圈回来了)。

上面的方法显然可能将环外的点也加进来,但是显然没有任何印象。

我们便可以将两种找环上点的方法结合起来,先每次 \(\log n\) 询问环上的单点0,然后每次 \(O(n)\) 倍增环上的点。由下图可知,一开始询问出长度为 \(56\) 链是最优的,当然上下浮动一点点也无伤大雅。

image

code:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int NN = 5e2 + 8;

int n,tot,rt = 1;

bool check(int tim,int l,int r){
	printf("? %d %d %d ",rt,tim,r - l + 1);
	for(int i = l; i <= r; ++i) printf("%d ",i);
	puts("");fflush(stdout);
	
	int op;scanf("%d",&op);
	return op;
}

set<int> Nodes;
bool vis[NN];

void Get_Part_Of_Ring(){
	int l = 1,r = n;
	while(l < r){
		int mid = (l + r) / 2;
		if(check(1000,l,mid)) r = mid;
		else l = mid + 1;
	}
	Nodes.insert(l);
	rt = l;
	for(int i = 1; i <= 62; ++i){
		l = 1; r = n;
		while(l < r){
			int mid = (l + r) / 2;
			if(check(1,l,mid)) r = mid;
			else l = mid + 1;
		}
		if(vis[l]) break;
		vis[l] = 1;
		Nodes.insert(l);
		rt = l;
	}
}
void Get_Ring(){//事实上应该包括了一部分的链 
	if(Nodes.size() != 63) return;//环找完了,在上一个函数里面  
    int sz = Nodes.size(); 
    while(1){
        tot = 0;
        for(int i = 1; i <= n; ++i) if(!vis[i]){
        	printf("? %d %d %d ",i,sz,Nodes.size());
            for(auto j : Nodes) printf("%d ",j);
            puts("");fflush(stdout);
            
            int op;scanf("%d",&op);
            if(op) vis[i] = 1,Nodes.insert(i);
        }//将环扩倍,当然也会有环外面链的部分被包含进来,但是无伤大雅 
        sz *= 2;
        if(sz > Nodes.size()) break;//有一部分重复了,环长没有扩倍,说明环找完了 
    }
}
void Get_Link(){
	for(int i = 1;i <= n;i++)if(!vis[i]){
        printf("? %d %d %d ",i,1000,Nodes.size());;
        for(auto j : Nodes) printf("%d ",j);
        puts("");fflush(stdout);
        
        int op;scanf("%d",&op);
        if(op == 1) Nodes.insert(i);
    }
}
void print(){
	printf("! %d ",Nodes.size());
	for(auto i : Nodes) printf("%d ",i);
    puts("");fflush(stdout);
}
int main(){
	scanf("%d",&n);
	Get_Part_Of_Ring();
	Get_Ring();
    Get_Link();
    print();
    return 0;
}

CF1854E Game Bundles

标签:思维题 \(A\)

你考虑我们需要构造出一组解,显然地这样的解有很多很多种(\({60^{60}}\) 显然是及其地大)。

那关键是我们如何进行构造。

我们很容易知道每个集合里面 \(> 30\) 的数只有一个。

所以我们可以在 \([1,30]\) 中随机 \(a_i\),直到满足的组数恰好小于等于 \(a_i\),添加的时候维护数组 \(f_i\) 表示和为 \(i\) 的集合 \(S\) 的个数。

我们可以发现,有些时候小的 \(a_i\) 如果很多,那么我们的答案增涨速度会很快,所以我们可以进行一个优化,每次随机一个 \(len\),然后让 \(a_i\)\([1,len]\) 中随机。

然后我们最后差的答案怎么补上呢?我们显然可以在序列中添加 \(> 30\) 的数,添加 \(i\) 这个数,显然会让 \(f_{60} = f_{60} + f_{60-i}\) 然后我们按 \(f_{60-i}\) 从大到小加入 \(i\) 这个数即可。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int NN = 2e5 + 8; 
mt19937 rnd(time(0));
int T,n,m;
ll a[NN],b[NN],p[NN];
ll f[NN];
int rdbt(int l,int r){return l + rnd() % (r-l+1);}
ll K,res;

int main(){
	scanf("%lld",&K);
	if(K <= 60){
		printf("%lld\n",K);
		for(int i = 1; i <= K; ++i) printf("60 ");
		puts("");
		return 0;
	}
	for(int i = 31; i <= 60; ++i) p[i] = i;
	while(1){
		for(int i = f[0] = 1; i <= 60; ++i) f[i] = 0;
		int k = rdbt(1,29);
		for(int i = 1; i <= n; ++i){
			a[i] = rdbt(1,k);
			if(f[60] + f[60-a[i]] > K){n=i-1;break;}
			for(int j = 60; j >= a[i]; --j) f[j] += f[j-a[i]];
		}
		res = K - f[60];
        sort(p+31 , p+61,[&](int x,int y){
            return f[60-x]>f[60-y];
        });
        if(res < 0) assert(0);
        for(int i = 31; i <= 60; ++i)
            while(n < 60 && res >= f[60-p[i]]) res -= f[60-p[i]], a[++n] = p[i];
        if(!res){
            printf("%d\n",n);
            for(int i = 1; i <= n; ++i) printf("%d ",a[i]);
            puts("");
            return 0;
        }
	}
}
posted @ 2023-09-08 15:44  ricky_lin  阅读(87)  评论(0)    收藏  举报