Codeforces Round #816 (Div. 2)补题(A-E)

A. Crossmarket

题目链接:Problem - A - Codeforces

 题意:小红和小蓝走网格,小红从左下角走到右上角,小蓝从左上角走到右下角,小红经过的网格会产生传送门,小蓝可以通过传送门一次行动次数在小红走过的路上任意传送,走一格也需要花一次行动次数,求最少行动次数。

思路:让小红先走完(先向上走,在向右走),此时固定花费n+m-2次行动次数,此时小蓝脚上有传送门,可以传送到同列最底部或者同行最右部,因此肯定传送max(n-1,m-1)的路程最赚(此时花费1行动次数),然后走min(n-1,m-1)次数。

结论:当n=m=1时,输出0,否则输出m+n-2+min(n-1,m-1)+1

代码实现:

#include <bits/stdc++.h>
#define ll long long
using namespace std;
signed main()
{
       ios::sync_with_stdio(0);
       cin.tie(0);
       cout.tie(0);
       int t;
	   cin>>t;
	   while(t--)
	   {
	   	int n,m;
	   	cin>>n>>m;
	   	if(n==1&&m==1)
	   	cout<<0<<'\n';
	   	else
	   	cout<<n+m-2+min(m,n)<<'\n';
		} 
}

B. Beautiful Array

题目链接:Problem - B - Codeforces

题意:给n,k,b,s,要寻找一个长度为n的数组,每个元素除以k向下取整的和等于b,数组元素的和等于s,如果没有就输出-1。

思路:给定的b,初步假设有b个k,分配到数组的各位置中,若此时数组和小于s,就对数组中元素增加大小,但每个元素增加的量不能超过k-1,因为超过的话k的数量就大于b了,因此一个数组增加的量最大为(k-1)*n,可以将b*ki的值全部放在一个位置,然后所有位置增加大小,将数组和变为s。

代码实现:

#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=1e5+10;
ll ans[maxn];
ll vis[maxn];
signed main()
{
        ios::sync_with_stdio(0);
        cin.tie(0);
        cout.tie(0);
        int t;
	    cin>>t;
	   	while(t--)
		{	
	   	ll n,k,b,s;
	   	cin>>n>>k>>b>>s;
	   	for(int i=1;i<=n;i++)
	   	ans[i]=0;
	   	ans[n]=b*k;//全放一起 
	   	ll temp=s-b*k;//此时数组和与s的差距 
		if(temp<0)
		{
			cout<<-1<<'\n';
			continue;
		 } 
	   		for(int i=1;i<=n&&temp;i++)
	   		{
	   			if(temp<=k-1)
	   			{
	   				ans[i]+=temp;
	   				temp=0;
				}
	   			else
			   {
					ans[i]+=k-1;
					temp-=k-1;	   	
			   }	
			}
		if(temp)//如果差距依旧存在,那么就找不到 
		cout<<-1<<'\n';
		else
		{
			for(int i=1;i<=n;i++)
			cout<<ans[i]<<' ';
			cout<<'\n';
		}
		} 
}

C. Monoblock

题目链接:Problem - C - Codeforces

题意:给一个数组,有一个函数g(l,r),返回数组[l,r]之间相邻相同元素组成的块的数量,进行m次操作,每次修改一个元素,并且要求输出此时对于所有(l,r)形成的g(l,r)的和。

思路:l,r的区间的个数总共为n*(n+1)/2,一个区间至少有一块,假设一开始数组元素都相同,那么所有区间g的和就为n*(n+1)/2,在此基础上,当出现两个相邻的元素不相同时,那么某些区间的g的值就会加1,考虑每个相邻的不同元素对产生贡献的区间的数量,假设i和i-1元素不同,那么产生贡献的区间的l可以在[1,i-1]中选择,r可以在[i,n]中选择,因此g的和增加(i-1)*(n-i+1),由此计算出未修改前的答案。

进行一次修改,考虑修改前后,该元素和相邻元素的关系,先撤销原来该元素与相邻元素对答案产生的贡献(当原来的元素和其相邻的元素不相同时),在结算修改后与相邻元素对答案的贡献(现在的元素与其相邻的元素不相同时)。

#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=1e5+10;
ll a[maxn];
signed main()
{
        ios::sync_with_stdio(0);
        cin.tie(0);
        cout.tie(0);
        int n,m;
        cin>>n>>m;
        for(int i=1;i<=n;i++)
        cin>>a[i];
        ll ans=0;
        ans+=(ll)(n+1)*n/2;//每个区间的g至少大于1 
        for(int i=2;i<=n;i++)
        if(a[i]!=a[i-1])
        ans+=(ll)(i-1)*(n-i+1);//相邻两个不同元素产生的贡献 
        while(m--)
        {
        	int x,y;
        	cin>>x>>y;
        	if(x>1&&a[x]!=a[x-1])//撤销该元素与前一个元素的贡献 
        	ans-=(ll)(x-1)*(n-x+1);
        	if(x<n&&a[x]!=a[x+1])//撤销该元素与后一个元素的贡献 
        	ans-=(ll)x*(n-x);
        	a[x]=y;
        	if(x>1&&a[x]!=a[x-1])//结算该元素与前一个元素的贡献 
        	ans+=(ll)(x-1)*(n-x+1);
        	if(x<n&&a[x]!=a[x+1])//结算该元素与后一个元素的贡献
        	ans+=(ll)x*(n-x);
        	cout<<ans<<'\n';
		}
}

D. 2+ doors

主要思路来源:Codeforces Round #816 (Div. 2) A - D - 知乎 (zhihu.com)

(大佬写的太好了)

题目链接:Problem - D - Codeforces

题意:n个元素的数组,给q个按位或的等式,要求寻找一个满足条件且字典序最小的数组。

思路:对于a|b=c,考虑c二进制形式上每一位的数字,如果c在第i位的数字为0,那么a和b对应位置必定都为0,如果c在第i位元素为1,那么a与b之间至少有1个数字对应位置要有1,目前不考虑之后的情况,假设只放1个1,为了追求字典序最小,这个1肯定放在后面一个元素上,但是对于一个式子a|b=c,a和b可能都要放1,这考虑起来就非常的麻烦。

将a|b=c对应建一条连接a和b权值为c的无向边,记录其关系,存完图后,对于一个c,0对应的a与b的位置必定为0,而1的位置,先两个都放1,进行双重循环,外循环遍历二进制位数,内循环1-n从前往后遍历。假设当前元素为ai,对于当前位数j是否放1,若此时已经为0了,就跳过,如果此时为1,考虑图中和他相邻的点,并且边的权值的第j位是1(对于j位和ai有关系,两者之间至少要有一个1),这些点的第j位的数字都是1的话,那么ai的第j位的1就不需要了,变为0。

存在a|a=c的情况,此时a=c,不需要再将1改成0。

代码实现

#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=1e5+10;
struct node{
	int to,v;
}; 
vector<node>vv[maxn];
inline void add(int u,int v,int w)
{
	vv[u].push_back({v,w});
	vv[v].push_back({u,w});
}
ll ans[maxn];
signed main()
{
        ios::sync_with_stdio(0);
        cin.tie(0);
        cout.tie(0);
        int n,m;
        cin>>n>>m;
        for(int i=1;i<=m;i++)
        {
        	int u,v,w;
        	cin>>u>>v>>w;
        	add(u,v,w);
		}
		for(int i=1;i<=n;i++)//初始都是1 
		ans[i]=(1<<30)-1;	
		for(int i=1;i<=n;i++)
		{
			for(int j=0;j<vv[i].size();j++)
			{
				int v=vv[i][j].v;
				ans[i]&=v;//a|b=c中,c的0的a和b对应位置也位0,而a和b其他位若不为0,那么就定为1 
			}
		}
		for(int j=30;j>=0;j--)
		{
			for(int i=1;i<=n;i++)
			{
				if((1<<j)&ans[i])
				{
					bool f=1;
					for(int k=0;k<vv[i].size();k++)
					{
						int to=vv[i][k].to;
						if(((1<<j)&ans[to])==0||to==i)//如果与之相连的点的对应位置为0或者有自己与自己按位或的式子(此时固定了) 
						{
							f=0;
							break;
						}
					}
					if(f)
					ans[i]-=(1<<j);//将j位的1变为0 
				}
			}
		}
		for(int i=1;i<=n;i++)
		cout<<ans[i]<<' ';
}

E. Long Way Home

题目链接:Problem - E - Codeforces

题意:n个城市,m条路,可以坐k次飞机,花费(u-v)*(u-v)时间,问从1到1-n所有城市的最短时间。

思路:首先考虑不坐飞机,那么就是简单的最短路问题,计算出每个点的dis后,考虑坐飞机。定义数组dp,用于记录先走一段路(坐飞机或者走路),最后坐飞机(可能不坐)到目的地,概念有点绕,主要是为了转移方程dp[u]=min(dp[u],dis[v]+(u-v)^2)(先到v,最后坐飞机(或者不坐)),然后再将dp的值传给dis,基于当前dis再进行最短路(飞机不一定只有最后坐,跑最短路寻求走路和坐飞机相间的最短时间),此时遇到一个问题,u和v要暴力遍历的话,一个点要遍历n次,那么时间复杂度就会达到n^2.

考虑斜率优化,首先推导公式:

temp1=dis[a]+(u-a)^2      temp2=dis[b]+(u-b)^2

我们要选择时间少的那个temp去和dp[u]作比较

假设temp1>temp2,那么

dis[a]+u^2-2ua+a^2>dis[b]+u^2-2ub+b^2

2u>((dis[b]+b^2)-(dis[a]+a^2))/(b-a)

右边式子形如k=(y2-y1)/(x2-x1)

对于一个点v,那么就将v定义成x,dis[v]+v^2定义成y。

将n个点都定义出对应的x和y画在坐标系上,横坐标x都是从1-n从小到大,对于两个点a,b:

当2u>k时(上面推的),b优于a,反之a优于b。但是此时u是(1-n)中的任意一个数,因此目前不能根据2u判断前后的优劣性。

对于三个点a,b,c形成的两个线段,考虑几种情况:

当前面的斜率k1>k2时,后期根据u结算时,若2u>k1,b优于a,那么2u>k2,此时c优于b,b点没有任何作用,若2u<k1时,a优于b,即使我不知道c与b的优劣性,b点依旧没有任何作用,所以可以直接删了。

当k1<k2时,若2u>k1,b优于a,不能确定2u>k2,无法确定b和c优劣性,若2u<k1,虽然说a优于b,但是不能删去b,因为u不能确定,b可能有时候会起作用。

因此利用先单调队列,建立斜率逐渐递增的坐标系,此时单调队列的head先不动,因为u不确定。

坐标系建完之后,遍历1-n所有的点,此时u是从小到大排序的,2u也是从小到大,因此对于一个u,可以直接根据其2u删除单调队列的首个元素(若开头两个点形成的斜率小于2u)。对于此时的u,最适合进行状态转移的点就为删除后的首个元素(斜率优化一般都这样)。

概要:求最短路->建立斜率递增的单调队列->根据从小到大的u删除队首元素->根据队首元素进行状态转移

按照以上步骤,进行k次循环

代码实现:

#include <bits/stdc++.h>
#define ll long long
#define int ll
#define double long double
using namespace std;
const int maxn=1e5+10;
const int inf=0x3f3f3f3f3f3f3f3f;
ll dp[maxn],dis[maxn];
bool vis[maxn];
int n,m,k;
struct edge{
	int to,v;
};
vector<edge>vv[maxn];
inline void add(int u,int v,int w)
{
	vv[u].push_back({v,w});
	vv[v].push_back({u,w});
}
struct node{
	int id,dis;
	friend bool operator<(const node&a,const node&b)
	{
		return a.dis>b.dis;
	}
};
void dij()//最短路板子 
{
	for(int i=1;i<=n;i++)
	vis[i]=0;
	dis[1]=0;
	priority_queue<node>q;
	for(int i=1;i<=n;i++)//唯一要注意的点,要把所有点放进队列,因为初始的dis不一定是inf 
	q.push({i,dis[i]});
	while(!q.empty())
	{
		node p=q.top();
		q.pop();
		int u=p.id;
		if(vis[u])
		continue;
		vis[u]=1;
		for(int i=0;i<vv[u].size();i++)
		{
			int to=vv[u][i].to;
			int w=vv[u][i].v;
			if(dis[to]>dis[u]+w)
			{
				dis[to]=dis[u]+w;
				q.push({to,dis[to]});
			}
		}
	}
}
//斜率优化部分 
double x[maxn],y[maxn];//存储x和y 
int que[maxn],head=1,tail=0;//单调队列 
inline double slope(int x1,int x2)//得到斜率 
{
	return (y[x2]-y[x1])/(x[x2]-x[x1]);
}
void solve()
{
	head=1;//初始化单调队列 
	tail=0;
	for(int i=1;i<=n;i++)
	{
		x[i]=i;
		y[i]=dis[i]+i*i;
		dp[i]=dis[i];
	}
	//head<tail是为了保留单调队列中的一个元素 
	for(int i=1;i<=n;i++)
	{
		while(head<tail&&slope(que[tail-1],que[tail])>slope(que[tail],i))//建立斜率单调递增的坐标系 
		tail--;
//		也可以这么写(可以画个图,两个互通的):
//		while(head<tail&&slope(que[tail-1],que[tail])>slope(que[tail-1],i)) 
//		tail--; 
		que[++tail]=i;
	}
	for(int i=1;i<=n;i++)
	{
		while(head<tail&&slope(que[head],que[head+1])<2*i)//根据2i删掉队首元素 
		head++;
		int j=que[head];
		dp[i]=min(dp[i],dis[j]+(j-i)*(j-i));//根据队首元素状态转移 
	}
	for(int i=1;i<=n;i++)
	dis[i]=dp[i];//记录答案 
}
signed main()
{
        ios::sync_with_stdio(0);
        cin.tie(0);
        cout.tie(0);
        cin>>n>>m>>k;
        for(int i=1;i<=m;i++)
        {
        	int u,v,w;
        	cin>>u>>v>>w;
        	add(u,v,w);
		}
		for(int i=1;i<=n;i++)
		dis[i]=dp[i]=inf;
		dp[1]=0;
		dis[1]=0;
		dij();
		for(int i=1;i<=k;i++)
		{
			solve();
			dij();
		}
		for(int i=1;i<=n;i++)
		cout<<dis[i]<<' ';
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值