CF Round #523 (Div. 2)

ACM题集:https://blog.csdn.net/weixin_39778570/article/details/83187443
题目链接:https://codeforces.com/contest/1061
官方题解:https://codeforces.com/blog/entry/63384

A题

题意:1,2,3,4,…n每个数都有无限个,问数k最少能由1~n中几个数组成

#include<bits/stdc++.h>
#define ll long long
using namespace std;

ll n,s;
int main(){
	scanf("%lld%lld",&n,&s);
	ll all=0;
	for(ll i=n; i>=1; i--){
		if(s>=i){
			all += s/i;
			s%=i;
		}
		if(s<=0)break;
	}
	cout<<all;
	return 0;
}

B题

题意:给定一个m列的柱状图,如m=3,图为4,5,6,表示连续的3个柱高(由方格组成)为4,5,6
这个柱状图在往上投影为3,往左投影为6
问,最多能去掉几个方格使得往上投影和往左投影与一开始一样。

解法:
把题目去掉格子,装换成填充格子(填充的格子是需要投影的),我们可以知道往上投影每一列放一个就能满足,但是往左投影需要所有列填放的高度(同一高度的不算)加起来与最高一列相同
m = 4; 序列5, 2, 2, 2
例如填充一个高度为5的可以这么填充
1 0 0 0
1 0 0 0
1 0 0 0
0 1 0 0
0 0 1 1
先排序好,从高到低进行填充,最后答案=方块数-填充数
枚举到哪一个时,就表示比当前高的全部填充过了
a[0]初始化为无穷大
枚举第i个时
当a[i] > a[i+1] 的时候,填充 a[i]-a[i+1]个
当a[i] == a[i+1] 的时候, 第i列填充1个,往后和他相等的都变为a[i]-1

#include<bits/stdc++.h>
#define ll long long
#define fo(i,j,n) for(register int i=j; i<=n; i++)
using namespace std;
ll n,m,a[100005],mx2,mx,mi=1e18,sum;
int main(){
	cin>>n>>m;
	fo(i,1,n){
		scanf("%I64d",&a[i]);
		sum += a[i];
	}
	sort(a+1,a+1+n);
	reverse(a+1,a+1+n);
	if(n==1)puts("0");
	else{
		ll ans = 0;
		a[0]=1e9+5;
		fo(i,1,n){
			if(a[i-1]<a[i])a[i]=a[i-1]; // 与前面高度持平 
			if(a[i]<=a[i+1]) { // 超过部分已经在前面通过若干个1填充过了 
				ans ++;	
				a[i]--; // 后面的 相同高度都变为a[i]-1 
			}
			else ans += max(1ll,a[i]-a[i+1]);
		}
		cout<<sum-ans;
	}
	return 0;
}

C题

题目:给定一个序列A,使用序列A可以构造序列B。序列B要满足这样的条件:
序列B: b 1 , b 2 , b 3 , b 4.. b n b1,b2,b3,b4..bn b1,b2,b3,b4..bn,且 1 ∣ b 1 , 2 ∣ b 2 , 3 ∣ b 3... n ∣ b n 1|b1,2|b2,3|b3...n|bn 1b1,2b2,3b3...nbn,也就是说bk要能被k整除
问最多有多少个B数组,B数组不同当有一个bk不同,或者B数组的长度不同。
解法:
d p [ i ] [ j ] dp[i][j] dp[i][j] 表示第i个数 a [ i ] a[i] a[i],数组B长度为j是的方案数
j j j a [ i ] a[i] a[i]的约数时,可以选或者不选 a [ i ] a[i] a[i]作为第 j j j个数
d p [ i ] [ j ] = d p [ i − 1 ] [ j ] + d p [ i − 1 ] [ j − 1 ] dp[i][j] = dp[i-1][j] + dp[i-1][j-1] dp[i][j]=dp[i1][j]+dp[i1][j1]
否则
d p [ i ] [ j ] = d p [ i − 1 ] [ j ] dp[i][j] = dp[i-1][j] dp[i][j]=dp[i1][j]
a n s = d p [ n ] [ i ] ans = dp[n][i] ans=dp[n][i] i ∈ [ 1 , m a x ( a [ i ] ) ] i ∈[1,max(a[i])] i[1,max(a[i])]
每个 a [ i ] a[i] a[i]在阶段 i i i只能使用一次,并且从阶段 i − 1 i-1 i1递推过来
可以通过反向递推 j j j 减低为1维
p [ i ] p[i] p[i] a [ i ] a[i] a[i]的因子,也就是说 p [ i ] p[i] p[i] a [ i ] a[i] a[i]所能填充的位置
从大到小枚举 p p p
d p [ p [ k ] ] = d p [ p [ k ] ] + d p [ p [ k ] − 1 ] dp[p[k]] = dp[p[k]] + dp[p[k]-1] dp[p[k]]=dp[p[k]]+dp[p[k]1]
表示选或者不选 a [ i ] a[i] a[i]的因子 p [ k ] p[k] p[k]作为第 p [ k ] p[k] p[k]个数

#include<bits/stdc++.h>
#define ll long long
#define fo(i,j,n) for(register int i=j; i<=n; ++i)
using namespace std;
const int mod = 1e9+7;
int n, a[100005],p1[100005],p2[100005];
ll f[1000005]; // f[n] = f[n] + f[n-1]
void solve(){
	f[0]=1;
	fo(i,1,n){ // 第i个数
		// 分解因子,这些因子决定了a[i]能放到第几个数 
		int t = sqrt(a[i]+0.5);
		int j,k=1,k2=1;
		for(j=1; j<=t; j++){
			if(j*j==a[i]){
				k2=k;
				p1[k++]=j;
				break;
			}
			if(a[i]%j==0){
				p1[k]=j;
				p2[k++]=a[i]/j;
			}
		}
		if(j*j!=a[i])k2=k;
		for(j=1; j<k2; j++) // 从大到小枚举,保证当前一轮,a[i]只使用一次 
			f[p2[j]] = (f[p2[j]]+f[p2[j]-1]) % mod;
		for(j=k-1; j>=1; j--)
			f[p1[j]] = (f[p1[j]]+f[p1[j]-1]) % mod;
	}
	ll ans = 0;
	fo(i,1,1000000) ans = (ans+f[i])%mod;
	cout<<ans;
} 
int main(){
	cin>>n;
	fo(i,1,n)scanf("%d",&a[i]);
	solve();
	return 0;
}

D题

题意:有n档节目,节目的播放时间为 [ L 1 , R 1 ] , [ L 2 , R 2 ] , [ L 3 , R 3 ] , . . . [ L n , R n ] [L1,R1],[L2,R2],[L3,R3],...[Ln,Rn] [L1,R1],[L2,R2],[L3,R3],...[Ln,Rn]
现在需要把所有的节目都播放完,租一台电视机的费用为x元(作为第一时刻的播放价格),往后每租单位时间的价格为y,也就是说租借 [ L k , R k ] [Lk,Rk] [Lk,Rk]这段时间一台电视机的花费为 x + ( R k − L k ) ∗ y x+(Rk-Lk)*y x+(RkLk)y,问:最少需要花费多少钱看完所有节目
5 4 3(n,x,y)
1 2[L,r]
4 10
2 4
10 11
5 9
Show [1,2] on the first TV,
Show [4,10] on the second TV,
Shows [2,4],[5,9],[10,11] on the third TV.
This way the cost for the first TV is 4+3⋅(2−1)=7, for the second is 4+3⋅(10−4)=22 and for the third is 4+3⋅(11−2)=31, which gives 60 int total.
解法:对于每个区间 [ L k , R k ] [Lk,Rk] [Lk,Rk],我们需要考虑新开一个区间的花费少,还是与某个区间 [ L i , R i ] [Li,Ri] [Li,Ri]合并的花费少.
也就是比较 x 和 ( L k − R i ) ∗ y x和(Lk-Ri)*y x(LkRi)y的大小,Ri为当前已经有的区间中最大的R,毕竟浪费的时间越少,花费的钱就越少

法一:93ms
先把区间按左端点排序
使用一个多重集合(可以看做是一个有序的vector),二分查找小于L的最大值
事先插入所有的右端点,就不用在线更新右端点了
因为查找的顺序是按区间的左端点排序好的
所以不会出现查找到一个还未出现的右端点,
因为还为出现的右端点一定比当前左端点大,所以无法查找到

#include <bits/stdc++.h>
using namespace std;
 
#define IOS ios::sync_with_stdio(0); cin.tie(0); cout.tie(0); //不能和scanf一起用 
#define endl "\n"
#define int long long

const int N=1e5+5;
const int MOD=1e9+7;

int n, x, y, ans=0;
int cost[N];
pair<int, int> a[N];
multiset<pair<int, int> > s;

int32_t main()
{
	cin>>n>>x>>y;
	for(int i=1;i<=n;i++) 
	{
		scanf("%I64d%I64d",&a[i].first,&a[i].second);
		s.insert({a[i].second, a[i].first}); // 把所有区间都加进去了,后面就不用加了,其实维护的主要还是右端点 
	}
	sort(a+1, a+n+1);
	for(int i=1;i<=n;i++)
	{
		cost[i] = (x + y * (a[i].second - a[i].first)); // 结构体指针要用->访问 
		if(!s.size() || s.begin() -> first >= a[i].first) // 最小的左端点>=右端点,二分查找找不到结果 
			continue;
		auto it = s.lower_bound({a[i].first, 0});  // 找到>=a[i].first第一个右端点 
		int pR = (--it) -> first; // 小于a[i]左端点的最大右端点 
		if (y * (a[i].second - pR) >= cost[i]) // 连接不如新开 
			continue;
		cost[i] = y * (a[i].second - pR); // 连接 
		s.erase(it);
	}
	for(int i=1;i<=n;i++)
	{
		ans+=cost[i];
		ans%=MOD;
	}
	ans%=MOD;
	cout<<ans;
	return 0; 
}

法二:93ms
和法一一样,只不过每次是在线插入右端点的

#include<bits/stdc++.h>
#define ll long long
#define fo(i,j,n) for(register int i=j; i<=n; ++i) 
using namespace std;
const int maxn = 1e5+5;
const int mod = 1e9+7;
ll n,x,y;
pair<ll,ll>a[maxn];
multiset<ll> R;
void solve(){
	sort(a+1,a+1+n);
	ll ans = 0,l,r;
	fo(i,1,n){
		l = a[i].first,r=a[i].second;
		if(R.empty()|| *(R.begin())>=l){ // 无法二分到答案
			ans += x+(r-l)*y;
			if(ans>=mod)ans%=mod;
			R.insert(r); // 在线插入 
			continue;
		}else{
			auto it = R.lower_bound(l); // >=的第一个值 
			int PR = *(--it); // 解引用 ,小于L的最大的值 
			if((l-PR)*y<x){
				ans += (r-PR)*y;
				if(ans>=mod)ans%=mod;
				R.insert(r); 
				R.erase(it); // 合并后要删除旧区间的右端点 
			}else{
				ans += x+(r-l)*y;
				if(ans>=mod)ans%=mod;
				R.insert(r); // 新开区间 
			}
		}
	}
	cout<<ans;
}
int main(){
	cin>>n>>x>>y;
	fo(i,1,n)scanf("%I64d%I64d",&a[i].first,&a[i].second);
	solve();
	return 0;
} 

法三:vector做法 780ms
与法一一样,但是换了中数据结构

#include<bits/stdc++.h>
#define ll long long
#define fo(i,j,n) for(register int i=j; i<=n; i++)
using namespace std;
const int MOD = 1e9+7;
int n,x,y;
vector<pair<ll,ll>> a;
vector<ll> R; // 存放所有右端点
ll cost[100005];
void solve(){
	sort(a.begin(),a.end());
	sort(R.begin(),R.end());
	fo(i,0,n-1){
		cost[i] = (x+y*(a[i].second-a[i].first));
		if(!R.size() || R[0]>=a[i].first)continue;
		auto it = lower_bound(R.begin(),R.end(),a[i].first);
		ll pR = R[(--it)-R.begin()];
		if(y*(a[i].second-pR)>=cost[i])continue;
		cost[i] = y*(a[i].second-pR);
		R.erase(it);  // 很耗时... 
	}
	ll ans=0;
	fo(i,0,n-1){
		ans+=cost[i];
		ans%=MOD;
	}
	ans%=MOD; 	
	cout<<ans;
}
int main(){
	cin>>n>>x>>y;
	ll l,r;
	fo(i,1,n){
		int l,r;
		scanf("%I64d%I64d",&l,&r);
		a.push_back(make_pair(l,r));
		R.push_back(r);		
	}
	solve();
	return 0;
}

法四:单调栈+优先列队做法 77ms
优先列队q,存放所有左端点
单调栈s,存放所有比L小的R,并且有序。
q是小根堆
维护s的方法:
首先对区间的左端点进行升序排序
对于每个[L,R],从小根堆里剔除比L小的R依次加到栈s中,这样每次加入栈s的R都是有序的
又由于L也是有序的,所以整个s也是有序的
每次剔除完R后,在线插入当前R

#include<bits/stdc++.h>
#define ll long long
#define fo(i,j,n) for(register int i=j; i<=n; i++)
using namespace std;
const int maxn = 1e5+5;
const int mod = 1e9+7;
ll n,x,y;
stack<ll> s; // 右区间单调栈,储存可能更新的答案 
priority_queue<ll,vector<ll>, greater<ll> > q; //  右小根堆 
pair<ll,ll> a[maxn]; // 左区间上升 
void solve(){
	sort(a+1,a+1+n); // 一定要排序 
	ll ans = 0,l,r;
	fo(i,1,n){
		l=a[i].first, r=a[i].second;
		while(!q.empty() && q.top()<l){
			s.push(q.top());q.pop(); // 假设单前为L1,那么压入栈的数小于L1,下一次压入栈的数介于 [L1,L2)之间 
		}							 // 整个栈单调递增 
		if(s.empty()){
			ans += x+(r-l)*y;
			if(ans>=mod)ans%=mod;
		}else{ // 小于l的最大的r 
			if((l-s.top())*y<x){
				ans += (r-s.top())*y;
				if(ans>=mod)ans%=mod;
				s.pop();
			}else{
				ans += x+(r-l)*y;
				if(ans>=mod)ans%=mod;
			}
		}
		q.push(r);
	}
	cout<<ans;
}
int main(){
	cin>>n>>x>>y;
	fo(i,1,n){
		scanf("%I64d%I64d",&a[i].first,&a[i].second);
	}
	solve(); 
	return 0;
}

F题

题意:有一个完美k叉树,且叶子节点高度都一样
可以向系统询问 ? a b c, 表示问:b是否在ac路径中间
通过不大于60n次询问找出根节点
节点和序号的不一致的
解法:
随机生成两个节点 V 1 , V 2 ( V 1 ! = V 2 , 1 &lt; = V 1 , V 2 &lt; = n ) V1,V2(V1!=V2,1&lt;=V1,V2&lt;=n) V1,V2(V1!=V2,1<=V1,V2<=n)
先找出两个叶子节点,两个叶子节点中间的点有2h-1个(h为高度)
然后找到一个点到和其中一个叶子节点 ,中间的点数有h-1个的点,那么该点就是跟节点

#include<bits/stdc++.h>
#define ll long long 
#define fo(i,j,n) for(register int i=j; i<=n; ++i) 
using namespace std;
int n,k;
vector<int> ver;
bool check(int v1 ,int i, int v2){
	printf("? %d %d %d\n",v1,i,v2);
	string s;
	cin>>s; // 会有空行,使用cin 
	return s[0]=='Y';
}
int main(){
	cin>>n>>k;
	int h=0,a=1,leaf=1;
	while(a<n){
		leaf *= k;
		a += leaf;
		h++; // 计算树高 
	}
	int v1,v2; // 左右两个叶子节点
	// 随机计算左右两个叶子节点 
	while(true){
		ver.clear();
		v1 = rand()%n + 1; // 1~n
		v2 = rand()%(n-1) + 1; // 1~n-1
		if(v1==v2)v2++;
		fo(i,1,n){
			if(i!=v1&&i!=v2&&check(v1,i,v2))ver.push_back(i);
		}
		if(ver.size() == 2*h-1)break; // 找到两个叶子节点 
	}
	for(int p:ver){
		int num = 0; // 记录p,v1之间的点数 
		for(int q:ver){
			if(p!=q && check(p,q,v1))num++;
		}
		if(num==h-1){ // 找到root了 
			printf("! %d\n",p); 
			break;
		}
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值