首页 > 其他分享 >CF1854 题解

CF1854 题解

时间:2023-09-04 22:23:41浏览次数:30  
标签:CF1854 int 题解 ll back 60 push dp

CF1854 题解

A

首先考虑只有非负的情况,次数完全可以接受 \(19\) 次,所以直接用 \(19\) 次做一次前缀和就可以保证单调不降了。

现在有了负数,考虑将负数变成正数,选出正数当中的最大值,然后用 \(a_i + a_i \to a_i\) 这样自增的方式让它的绝对值大于负数最大值,因为绝对值小于等于 \(20\) ,所以最多 \(5\) 次就可以达到。与其在一个负数上加很多次,显然不如先将正数加到 \(20\) 以上,再只加一次得到结果,但是我们只有 \(31\) 次,减去前缀和 \(19\) 次和这 \(5\) 次,我们还有 \(7\) 次,就是可以加 \(7\) 个负数,那么如果负数太多怎么办呢?

考虑到如果将整个数列取反,就相当于要构造一个不升序列,再 \(reverse\) 过来,就又变成了原问题,但是原来的正负数个数交换了,在上述情况 \(7\) 个负数以上的情况时,既然我们正数要自增 \(5\) 次,那么负数最大值一定大于正数最大值,所以我们翻转后无需自增,直接将新的负数加成正数就好了,次数是 \(19 + 12 = 31\) 次,这个 \(12\) 是因为上段情况不成立时原来的负数一定超过 \(8\) 个,那么新的负数一定小于等于 \(12\) 个,由此得证。

#include<bits/stdc++.h>
using namespace std;
int n,a[25],pa[25];
vector <pair<int,int> > pos;
inline void solve()
{
	int maxpos = 0,minneg = 0,maxi = 0;
	for(int i = 1;i <= n;i++) maxpos = max(maxpos,a[i]),minneg = min(minneg,a[i]);
	if(maxpos == 0 && minneg != 0)
	{
		for(int i = 1;i <= 32;i++) pos.push_back(make_pair(1,1));
		return;
	}
	for(int i = 1;i <= n;i++) if(maxpos == a[i]) maxi = i;
	while(maxpos < -minneg) pos.push_back(make_pair(maxi,maxi)),a[maxi] *= 2,maxpos *= 2;
	for(int i = 1;i <= n;i++) if(a[i] < 0) pos.push_back(make_pair(i,maxi)),a[i] += a[maxi];
	for(int i = 2;i <= n;i++) if(a[i] < a[i - 1]) pos.push_back(make_pair(i,i - 1)),a[i] += a[i - 1];
}
int main()
{
	int T;
	cin>>T;
	while(T--)
	{
		cin>>n;
		for(int i = 1;i <= n;i++) cin>>a[i];
		for(int i = 1;i <= n;i++) pa[i] = a[i];
		solve(); 
		if(pos.size() > 31) 
		{
			pos.clear();
			for(int i = 1;i <= n;i++) a[i] = -pa[i];
			reverse(a + 1,a + n + 1);
			solve();
			cout<<pos.size()<<endl;
			for(auto i : pos) cout<<n + 1 - i.first<<" "<<n + 1 - i.second<<endl;
		}
		else
		{
			cout<<pos.size()<<endl;
			for(auto i : pos) cout<<i.first<<" "<<i.second<<endl;
		}
		pos.clear();
	}
	return 0;
}

B

考虑到一个关键性质:得分和摸牌其实是同一个东西,再加上我们摸到的牌一定是一个区间 \([1,k]\) ,我们假设最后摸了 \(k\) 张牌,那么答案就是 \(\sum_{i = 1}^{k}a_i - (k - 1)\)。相当于摸了 \(k\) 张牌就一定有 \(k - 1\) 个值被拿来摸牌。

所以我们只需要知道能否有一种方案,使得最后恰好摸了 \(k\) 张牌即可,假设 \(dp_i\) 为能否摸到 \(i\) 张牌,每次枚举 \(a_i\) ,有转移: \(dp_j = dp_j\ |\ dp_{j - a_i}\) 。

我们注意到 \(dp\) 值是一个 \(bool\) 类型,和 \(10^5\) 的数据、 \(3s\) 的时长。

考虑用 \(bitset\) 优化,每次将 \(dp\) 数组整体左移 \(a_i\) 再或一下即可。注意就算超过了 \(n\) 也要减掉,所以数组大小开两倍。

#include<bits/stdc++.h>
using namespace std;
const int N = 1e5 + 5;
bitset <N * 2> dp,able;
long long a[N],s[N],n;
int main()
{
	cin>>n;
	for(int i = 1;i <= n;i++) cin>>a[i],s[i] = a[i] + s[i - 1];
	dp[1] = 1;able[1] = 1;
	for(int i = 1;i <= n;i++) 
	{
		dp |= (dp << a[i]);
		able[i + 1] = dp[i + 1];
		dp[i] = 0;
	}
	long long ans = 0;
	for(int i = 1;i <= n;i++) if(able[i]) ans = max(ans,s[i] - i + 1);
	for(int i = n + 1;i <= 2 * n;i++) if(dp[i]) ans = max(ans,s[n] - i + 1);
	cout<<ans;
	return 0;
}

C

期望 \(dp\) 题目,这题不好设状态,于是我们一个一个条件来。考虑没有“相撞”的答案,就相当于每个数到 \(m + 1\) 的距离之和。即 \(\sum_im + 1 - a_i\) 。由于有了相撞,我们多算了一些东西,考虑到相撞的概率只和 \(3\) 个元素有关:相撞的两个数和相撞位置,设数为 \(x,y\) ,位置为 \(z\) 。那么期望就要减去 \(p_{x,y,z} \times (m + 1 - z)\) 。

发现只有相邻的两个数字会相撞(不是的话可以等效)。所以考虑每一对数 \(a_i,a_{i + 1}\) 。设 \(f_{x,y}\) 表示两个数分别在 \(x,y\) 位置的概率,首先将 \(f_{a_i,a_{i + 1}}\) 设为 \(1\) 。然后由于只考虑两个数的移动,分别以 \(\frac 12\) 的概率将 \(f_{x,y}\) 转移到 \(f_{x + 1,y},f_{x,y + 1}\) 即可。从小到大枚举左端点,那么 \(f_{x,x}\) 就是两数在 \(x\) 处相撞的概率。时间复杂度 \(O(nm^2)\) 。

由于最后我们要将减掉的东西加起来,所以根据期望线性性,我们可以将每一对数一起处理,只用 \(dp\) 一遍,时间复杂度 \(O(m^2)\) 。

#include<bits/stdc++.h>
using namespace std;
const int N = 505,MOD = 1e9 + 7;
typedef long long ll;
ll a[N],n,m,dp[N][N];
inline ll ksm(ll base,ll pts)
{
	ll ret = 1;
	for(;pts > 0;pts >>= 1,base = base * base % MOD)
		if(pts & 1)
			ret = ret * base % MOD;
	return ret;
}
int main()
{
	cin>>n>>m;
	ll sig = 0;
	for(int i = 1;i <= n;i++) cin>>a[i],sig += (m + 1 - a[i]);
	memset(dp,0,sizeof(dp));
	for(int i = 1;i <= n - 1;i++) dp[a[i]][a[i + 1]] = 1;
	ll ans = sig;
	for(int i = 1;i <= m;i++)
	{
		ans = (ans - dp[i][i] * (m + 1 - i) % MOD + MOD) % MOD;
		for(int j = i + 1;j <= m;j++)
		{
			ll iv = ksm(2,MOD - 2);
			dp[i + 1][j] = (dp[i + 1][j] + iv * dp[i][j] % MOD) % MOD;
			dp[i][j + 1] = (dp[i][j + 1] + iv * dp[i][j] % MOD) % MOD;
		}
	}
	cout<<ans;
	return 0;
}

D

题意就是求出 \(1\) 所在树的所有节点,考虑如果找出环上的每一个点,就可以询问其他所有点,如果 \(x\) 走 \(n\) 步可以到达环上的点,那么就在这棵树中。

首先,我们用一个二分法找到环上的第一个点,我们将已有的点集分为两部分,询问 \(1\) 是否可以走 \(n\) 步到达左半边,如果可以就取左半边,否则就取右半边。这样每次询问次数是 \(\lceil \log 500\rceil = 9\) 。

已知环上的一个点集 \(G\) ,我们有两种做法:

1.我们可以用上次询问的点 \(x\) ,观察它一步走到的是哪个点,这个点一定在环上,如果这个点已经被访问过,就说明已经找到了整个环,但是这样次数是 \(size \times 9\) 的,太多。

2.我们可以使用倍增,每次暴力扫描每个点,观察到目前点集 \(G\) 是否可以走 \(|G|\) 步到达。如果可以,要么是环上的前 \(|G|\) 个点,要么是树上的一些节点,但是树上的点最后还是要计入答案,所以不影响。这样的次数是 \(n - 1 + n - 2 + n - 4 + n - 8 + \dots\) ,还是太多。

我们发现,前一种方法适用于 \(|G|\) 很小的情况,后面适用于 \(|G|\) 很大的情况,于是整合这两个方法,就可以控制询问在 \(2000\) 次以内,计算得出第一种方法暴力找 \(63\) 个点时最优。最后询问每个点能否走 \(n\) 步到 \(G\) 即可。

#include<bits/stdc++.h>
using namespace std;
const int N = 2005,p = 63;
int vis[N],n;
vector <int> G;
inline int q(int u,int k,vector <int> v)
{
	cout<<"? "<<u<<" "<<k<<" "<<v.size()<<" ";
	for(auto in : v) cout<<in<<" ";
	cout<<endl;
	int ret;
	cin>>ret;
	return ret;
}
inline int ask1()
{
	vector <int> l,r,d;
	for(int i = 1;i <= n;i++) d.push_back(i);
	while(d.size() > 1)
	{
		l.clear();r.clear();
		for(int i = 0;i < d.size() / 2;i++) l.push_back(d[i]);
		for(int i = d.size() / 2;i < d.size();i++) r.push_back(d[i]);
		d.clear(); 
		if(q(1,n,l)) for(auto in : l) d.push_back(in);
		else for(auto in : r) d.push_back(in);
	} 
	return d[0];
}
inline int ask(int x)
{
	vector <int> l,r,d;
	for(int i = 1;i <= n;i++) d.push_back(i);
	while(d.size() > 1)
	{
		l.clear();r.clear();
		for(int i = 0;i < d.size() / 2;i++) l.push_back(d[i]);
		for(int i = d.size() / 2;i < d.size();i++) r.push_back(d[i]);
		d.clear();
		if(q(x,1,l)) for(auto in : l) d.push_back(in);
		else for(auto in : r) d.push_back(in);
	} 
	return d[0];
}
int main()
{
	ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
	cin>>n;
	G.push_back(ask1());
	vis[G[0]] = 1;
	for(int i = 2;i <= p;i++) 
	{
		int ret = ask(G[i - 2]);
		if(vis[ret] == 1) break;
		else vis[ret] = 1,G.push_back(ret);
	}
	if(G.size() == p)
	{
		int shz = G.size();
		while(1)
		{
			for(int i = 1;i <= n;i++)
			{
				if(vis[i]) continue;
				if(q(i,shz,G)) G.push_back(i),vis[i] = 1;
			}	
			shz *= 2;
			if(shz > G.size()) break;
		}	
	}
	for(int i = 1;i <= n;i++)
	{
		if(vis[i]) continue;
		if(q(i,n,G)) G.push_back(i),vis[i] = 1;
	}
	cout<<"! "<<G.size()<<" ";
	for(auto i : G) cout<<i<<" ";
	cout<<endl;
	return 0;
}

E

考虑随机化,子序列中大于 \(30\) 的数最多只有一个,所以我们 \(rand\) 前 \(30\) 个数,要求 \(\leq 30\) ,然后求出 \(f_i\) ,表示子序列和等于 \(i\) 时的情况数。然后将 \(31 - 60\) 按照 \(f_{60 - i}\) 从大到小排序,然后只要 \(m\) 还大于 \(f_{60 - p_i}\) ,就在序列末尾加上一个 \(p_i\) 。如果最后凑到了 \(0\) ,就输出方案,不然就全部重来。考虑最终方案有很多种,所以玄学地可以 \(AC\) 。

#include<bits/stdc++.h>
using namespace std;
const int N = 105;
typedef long long ll;
ll m,dp[N],a[N],p[N],n,top;
inline int qr(int l,int r)
{
	return l + rand() % (r - l + 1);
}
inline bool cmp(int x,int y){return dp[60 - x] > dp[60 - y];}
int main()
{
	srand(time(NULL));
	cin>>m;
	if(m <= 60)
	{
		cout<<m<<endl;
		for(int i = 1;i <= m;i++) cout<<"60 ";
		return 0;
	}
	for(int i = 1;i <= 30;i++) p[i] = 30 + i;
	while(1)
	{
		memset(dp,0,sizeof(dp));
		dp[0] = 1;
		n = 0;
		top = qr(2,30);
		for(int i = 1;i <= 60;i++)
		{
			a[++n] = rand() % top + 1;
			if(dp[60] + dp[60 - a[n]] > m) {n--;break;}
			for(int j = 60;j >= a[n];j--) dp[j] += dp[j - a[n]];
		}
		ll lft = m - dp[60];
		sort(p + 1,p + 31,cmp);
		for(int i = 1;i <= 30;i++)
			while(n < 60 && lft >= dp[60 - p[i]]) a[++n] = p[i],lft -= dp[60 - p[i]];
		if(!lft)
		{
			cout<<n<<endl;
			for(int i = 1;i <= n;i++) cout<<a[i]<<" ";
			return 0;
		}
	}
	return 0;
 } 

F

咕。

标签:CF1854,int,题解,ll,back,60,push,dp
From: https://www.cnblogs.com/TheLastCandy/p/17678246.html

相关文章

  • 【题解】NOIP2022
    怎么看T3也不是那么难,可是为啥赛时就是被卡死了[难过]不补\(B\)题了,ad-hoc。A.种花题目描述:小C决定在他的花园里种出\(\texttt{CCF}\)字样的图案,因此他想知道\(\textttC\)和\(\textttF\)两个字母各自有多少种种花的方案;不幸的是,花园中有一些土坑,这些位置无法种......
  • AT318 A-G 题解
    A枚举\(1\simn\)的每个数,判断是否有\(i-M\equiv0\pmodP\)即可。赛时代码B暴力覆盖即可,注意\(x,y\)均是左开右闭。赛时代码C贪心的想,如果要替换\(i\)项,那必然是替换最大的\(i\)项,因此只需要对\(f\)排序,预处理后缀和后再扫一遍取替换前\(i\)项的最小值即可......
  • 湖北省选模拟 2023 部分题解
    质量不错。为什么湖北会有这么hard的省选啊/fn。D1T1\(\color{Gold}\bigstar\)第一题就不会是我没想到的。考虑一下简单情况,一条链咋做,每次操作相当于把一个空隙的大小减小\(2\),因此可以进行一个dp。具体咋dp,先咕。然后考虑只有一个环咋做,如果是偶环,那么肯定是一直操......
  • 【 LeetCode题解 】203. 移除链表元素
    【LeetCode题解】203.移除链表元素题目链接:https://leetcode.cn/problems/remove-linked-list-elements/博客主页链接:DuckBro博客主页关注博主,后期持续更新系列文章***感谢观看,希望对你有所帮助***目录【LeetCode题解】203.移除链表元素......
  • Xcode,swift:Error Domain=kCLErrorDomain Code=1 "(null)"问题解决
    问题描述:iOS开发时,当使用用户的位置权限时,获取用户经纬度报错:ErrorDomain=kCLErrorDomainCode=1"(null)",错误域=kCLError域代码=1“(null)”解决方法:打开模拟机的设置-通用-语言与地区将地区设置为中国(如果你的开发位置在中国的话) 点击左上方Features,选择Locati......
  • 网络规划设计师真题解析--交换机(一)(STP选择过程)
    下图所示是一个园区网的一部分,交换机A和B是两台接入层设备,交换机C和D组成核心层,交换机E将服务器群连接至核心层。如图所示,(2014年真题)如果采用默认的STP设置和默认的选举过程,其生成树的最终结果为(1),ABCD这时候交换机B上的一台工作站,想要访问交换机E上的服务器的路径是(2),A.B-D-E......
  • SqlServer2000数据库迁移"用户已存在"问题解决
    作者:fbysss关键字:sqlserver数据库用户,关联缺失背景:数据库从另外一台服务器备份之后还原,发现程序中登录数据库失败。排查:发现"安全性"->"登录"中的数据库用户与数据库没有关联,但是手工再关联,却报出错误21002:[sql-dmo]用户***已经存在的异常信息。而删除该数据库用户也无法进行,因为......
  • [CF1854C] Expected Destruction
    题目描述Youhaveaset$S$of$n$distinctintegersbetween$1$and$m$.Eachsecondyoudothefollowingsteps:Pickanelement$x$in$S$uniformlyatrandom.Remove$x$from$S$.If$x+1\leqm$and$x+1$isnotin$S$,add$x+......
  • 洛谷P3808 【模板】AC 自动机(简单版)题解 AC自动机模板题
    题目链接:https://www.luogu.com.cn/problem/P3808AC自动机模板题。示例程序:#include<bits/stdc++.h>usingnamespacestd;constintmaxn=1e6+5;structNode{intson[26],fail,id;Node(){}Node(int_id){memset(son,0,sizeof(son));......
  • 【牛客周赛 Round 10】A-D题解
    Ahttps://ac.nowcoder.com/acm/contest/64272/A题意游游定义一个数组为“稳定的”,当且仅当数组相邻的两个元素之差的绝对值不超过1。例如[2,3,2,2,1]是稳定的,而[1,3,2]则不是稳定的。游游拿到了一个数组,她想求出该数组的最长的“稳定的”连续子数组的长度。题解首先,如果在某......