Codeforces Round #592 (Div. 2) (补题)(C、D、E)

心得

有的时候,的确是缺乏休息吧,赛前两天好好休息休息

本来觉得挺难写的,睡了一觉发现这都是什么sb题……

C - The Football Season(枚举/exgcd)

1<=n<=1e12,0<=p<=1e17,1<=d<w<=1e5

输出任一组非负三元组解(x,y,z)满足x⋅w+y⋅d=p且x+y+z=n,无解输出-1

显然x+y越小越好,w>d时贪心x越大越好y越小越好,

取y非负最小解即可,扩欧姿势不对会炸ll,

要先约一下w和d的gcd,求y*d=1(mod w)的逆元y

把p模过w之后再乘y,能模就模,

但注意到,y的解不会超过w(否则可以不断执行y-=w,x+=d),枚举y更新答案不会炸

D - Paint the Tree(线性dp)

用0 1 2三种颜色染n(n<=1e)节点的树,第i个染法对于第j个节点有代价c[i][j]

树上相邻三元组(x,y,z)(即x与y邻接,y与z邻接)颜色不同条件下,输出最小代价和

自己dp太麻烦,看了下别人的代码,茅塞顿开

显然度>=3无解,否则就是一条链,dfs处理塞进vector,

根本不用dp[N][6]六种状态或dp[N][3][3]枚举这一位填什么上一位填什么转移最后倒着扫一遍dp序列反向求路径

对于链序列,答案只可能是

0 1 2 0 1 2 ... 或 0 2 1 0 2 1 ... 或 1 0 2 1 0 2 ...

共6种,所以暴力跑下全排列,能更新就记录下路径

#include<bits/stdc++.h>
#define pb push_back
using namespace std;
typedef long long ll;
const int N=1e5+10;
bool ok;
int n,num[3],rt,c[3][N],u,v;
vector<int>E[N],f;
int ans[N];
void dfs(int x,int fa)
{
	f.pb(x);
	for(int v:E[x])
	{
		if(v==fa)continue;
		dfs(v,x);
	}
}
ll solve(int x)
{
	f.pb(0);
	dfs(x,-1);
	for(int i=0;i<3;++i)
		num[i]=i; 
	ll res=8e18;
	do
	{
		ll tmp=0;
		for(int i=1;i<=n;++i)
		tmp+=c[num[i%3]][f[i]];
		if(res>tmp)
		{
			res=tmp;
			for(int i=1;i<=n;++i)
			ans[f[i]]=num[i%3];
		}
	}while(next_permutation(num,num+3));
	return res;
}
int main()
{
	scanf("%d",&n);
	for(int j=0;j<3;++j)
	{
		for(int i=1;i<=n;++i)
		{
			scanf("%d",&c[j][i]);
		}
	}
	for(int i=1;i<n;++i)
	{
		scanf("%d%d",&u,&v);
		E[u].pb(v),E[v].pb(u);
	}
	for(int i=1;i<=n;++i)
	{
		if(E[i].size()>=3)
		{
			ok=1;
			break;
		}
		if(E[i].size()==1)rt=i;
	}
	if(ok)puts("-1");
	else
	{
		printf("%I64d\n",solve(rt));
		for(int i=1;i<=n;++i)
		printf("%d%c",1+ans[i]," \n"[i==n]);
	}
	return 0;
} 

E. Minimizing Difference(枚举二分/贪心双指针)

n(2<=n<=1e5)个数,第i个数ai(1<=ai<=1e9),

有k(1<=k<=1e14)次机会,用一次机会可以将一个数+1或-1

求合理使用后,最大数mx-最小数mn的值最小是多少

题解做法,枚举+二分,

考虑最后结果[mn,mx]如果一个端点和原序列不重合,

那么说明此时把所有等于mn的数+1/-1和把所有等于mx的数+1/-1的代价相同,

因此,总可以左右滑动这个区间,使得这个区间和一个端点重合

枚举最大值up,先把大于up的都调到up,再二分找到最小值下界down,

判一个二分的最小值mid是否合法时,套二分找到小于下界的最大位置pos,

[1,pos]内数之和应被调至pos*mid,前缀和维护下子段和

再用相同方法做一遍枚举最小值,复杂度O(n*logn*logn)

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e5+10;
ll n,k,a[N],sum[N],ans,pos;
ll up,down,l,r,L,R,mid,nowk;
int main()
{
	scanf("%I64d%I64d",&n,&k);
	for(int i=1;i<=n;++i)
	scanf("%I64d",&a[i]);
	sort(a+1,a+n+1);
	for(int i=1;i<=n;++i)
	sum[i]=sum[i-1]+a[i];
	ans=8e18;
	for(int i=n;i>=1;--i)
	{
		up=a[i];
		l=a[1],r=a[i];
		nowk=k-((sum[n]-sum[i])-(n-i)*up);
		if(nowk<0)continue;
		while(l<=r)
		{
			mid=(l+r)/2;
			pos=lower_bound(a+1,a+i+1,mid)-a;
			if(a[pos]>mid)pos--;
			if(pos*mid-sum[pos]<=nowk)l=mid+1;
			else r=mid-1;
		}
		ans=min(ans,up-r);
	}
	for(int i=1;i<=n;++i)
	{
		down=a[i];
		l=a[i],r=a[n];
		nowk=k-(i*down-sum[i]);
		if(nowk<0)continue;
		while(l<=r)
		{
			mid=(l+r)/2;
			pos=lower_bound(a+i+1,a+n+1,mid)-a;
			if(sum[n]-sum[pos-1]-(n-pos+1)*mid<=nowk)r=mid-1;
			else l=mid+1;
		}
		ans=min(ans,l-down);
	}
	printf("%I64d\n",ans);
	return 0;
} 

考虑贪心+双指针O(n)做法,不断将小值向上拔,大值向下压,

如果当前有i个值相等为mn,j个值相等为mx,

显然,i<=j时拔mn,i>j时降mx,

个数变化,仅发生在和序列中下一个数合并时,i++/j++

因此,每次判断当前机会数k是否足以与下一个数合并,

不能时,说明不足以跳到下一个数,跳到中间之后break即可

由于要排个序,最后还是O(nlogn)

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e5+10;
ll n,k,a[N];
ll up,down,l,r;
ll pre,suf;
int main()
{
	scanf("%I64d%I64d",&n,&k);
	for(int i=1;i<=n;++i)
	scanf("%I64d",&a[i]);
	sort(a+1,a+n+1);
	l=1,r=n;
	down=a[1];up=a[n];
	while(l<r)
	{
		while(l<r&&a[l+1]==a[l])l++;
		while(r>l&&a[r-1]==a[r])r--;
		pre=a[l+1]-a[l];
		suf=a[r]-a[r-1];
		if(l<=n-r+1)
		{
			if(l*pre<k)
			{
				k-=l*pre;
				l++;				
				down=max(down,a[l]);
			}
			else
			{
				down+=k/l;
				break;
			}
		}
		else
		{
			if((n-r+1)*suf<k)
			{
				k-=(n-r+1)*suf;
				r--;
				up=min(up,a[r]);
			}
			else
			{
				up-=k/(n-r+1);
				break;
			}
		}
	}
	printf("%I64d\n",up-down);
	return 0;
} 
发布了467 篇原创文章 · 获赞 53 · 访问量 4万+

猜你喜欢

转载自blog.csdn.net/Code92007/article/details/102575394