Codeforces Round #503 (by SIS, Div. 2) 补题

A. New Building for SIS

题意:告诉我们楼的高度和数量,且这些楼的某些连续固定楼层之间是有通道的,问从最左边的到最右边的需要走多少步。

a~b之间的楼层是一定有通道的。

b------- --------- --------  --------

a------- --------- --------  --------

我们已知从左边走到右边,经过通道的距离s始终都是tower2-tower1.

分情况讨论:假设左边初始楼层floor1<右边终止楼层floor2.

(1)floor2<a:距离=l+s=a-floor2+a-floor1+s;

(2)floor1>a:距离=l+s=floor1-b+floor2-b+s;

(3)剩余的情况,就是初始楼层或者终止楼层在a~b中,所以竖直方向上移动的距离只是floor2-floor1。

最后两种情况列举出来是相似的所以代码如下:

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<cmath>

using namespace std;

int main()
{
	long long a,b;
	long long n,h;
	cin>>n>>h>>a>>b;
	long long k;
	cin>>k;
	long long towl1,floor1;
	long long towl2,floor2;
	long long ans;
	for(int i=1;i<=k;i++)
	{
		ans=0;
		cin>>towl1>>floor1;
		cin>>towl2>>floor2;
		if(towl1!=towl2)
		{
        if(floor1<floor2)
        {
        	if(a>floor2)ans+=(a-floor2)+(a-floor1)+abs(towl1-towl2);
        	else if(b<floor1)ans+=(floor1-b)+(floor2-b)+abs(towl1-towl2);
        	else ans+=(floor2-floor1)+abs(towl1-towl2);
        }
        else
        {
      		if(a>floor1)ans+=(a-floor1)+(a-floor2)+abs(towl1-towl2);
        	else if(b<floor2)ans+=(floor1-b)+(floor2-b)+abs(towl1-towl2);
        	else ans+=(floor1-floor2)+abs(towl1-towl2);
        }
		}
		else ans=abs(floor1-floor2);
        cout<<ans<<endl;
	}
}

  B. Badge

题意:一共有n个学生,老师不知道谁犯了错。怪罪到第i个学生上,他会继续怪罪到ai那个学生上,如果发现有一个学被怪罪了两次那么这个学生就是罪人。给出n个学生对应的ai,并且输出第一个怪罪的是i这个学生最后谁是罪人(bi),输出一串序列。

问题的关键就是模拟,造一个数组记录ai,再造一个数组记录被怪罪了几次,还有一个数组记录罪人。

任何做一个循环:怪罪一个人就给怪罪次数+1,然后移动到ai那个人上去,一旦遇到两次怪罪次数就直接退出循环。

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>

using namespace std;

int main()
{
	int A[2001];
	int B[2001];
	int ans[2001];
	int n;
	cin>>n;
	int k;
	memset(B,0,sizeof(B));
	for(int i=1;i<=n;i++)
	{
		cin>>A[i];
	} 
	for(int i=1;i<=n;i++)
	{
		memset(B,0,sizeof(B));
		B[i]=1;k=A[i];
        while(1)
        {
			B[k]++;
			if(B[k]==2)
			{
				ans[i]=k;
				break;
			}
			k=A[k];
        }
	}
	for(int i=1;i<=n;i++)
	{
		cout<<ans[i]<<" ";
	}
}

C. Elections

题意:几个党派,你作为党派1,现在n个人投给了自己喜欢的党派,每个人需要用一定的金钱贿赂才可以投给你,你需要花多少钱才能赢得选举。

解决办法就是枚举票数。

单独的贪心不可能解决,在不知道多少票数获胜的情况下,可能你是拿了很多票赢了比你高的人,也有可能只是从比你高的人拿了票以相对较低的票数也可以获胜。//这就是直接贪心会错的反例。

枚举票数就可以解决这个问题,枚举票数然后比你票数高的人贿赂费一定要花,如果花完之后票数不够从比你票数低的人里面贪心取。//这个时候取不需要考虑票数变化对胜利的影响。

代码1:自己写的代码和注释,相对较为繁琐。

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<map>
#include<vector>
#define LL long long
using namespace std;

LL ans[5000];//-答案- 

int F[5000];//是否使用
int E[5000];//
int party[5000];//党派已投票的数 
int P[5000];//枚举票数过程中的 
 
struct node1
{
	LL w;
	int q;
}; 

struct node2
{
	vector<node1>index; 
}A[5000];//党派所对应的投票者和贿赂价格 

int cmp(node1 a,node1 b)
{
	return a.w<b.w;
}
node1 bns[5000]; 
int main()
{
	memset(E,0,sizeof(E));
	int n,m,x;
	cin>>n>>m;
	LL y;
	int M=-1;
	for(int i=1;i<=n;i++)
    {
    	cin>>x>>y;
    	party[x]++;
    	node1 temp;
    	temp.w=y;
    	temp.q=i;
    	bns[i].w=y;
    	bns[i].q=i; 
    	A[x].index.push_back(temp);
    	if(x==1)
    	E[i]=1;//标记出党派1已有的投票数 
    }
    
	for(int i=1;i<=m;i++)
    {
//    	cout<<party[i]<<endl;
    	sort(A[i].index.begin(),A[i].index.end(),cmp);
    	M=max(M,party[i]);
    }
    
    sort(bns+1,bns+1+n,cmp);
    M+=1;//最大可能的票数
	int ret;//现有票数和枚举票数的差别 
	int flag;
	//标记 
	LL pre;//每一次达成枚举票数的贪心结果 
	int cnt;
	int cnt2=0; 
	for(int i=party[1]>1?party[1]:1;i<=M;i++)
	{
		flag=1;pre=0;
		memcpy(F,E,sizeof(E));
		memcpy(P,party,sizeof(party));
		ret=i-party[1];
		for(int j=2;j<=m;j++)
		{
			cnt=0;
			while(P[j]>=i)//票数高于枚举票数的都会导致最后不成功 
			{
			//	cout<<P[j]<<endl;
				P[j]--;
				ret--;
				pre+=A[j].index[cnt].w;
				F[A[j].index[cnt].q]=1;
				cnt++;
			}
			if(ret<0)
			{
				flag=0;
				break;
			}
		}
			for(int j=1;j<=n;j++)
		      if(!F[bns[j].q])
			  {
			     if(ret==0)break;
				 ret--;
				 pre+=bns[j].w;
			  }//用于加上较低票数的价格。 
			 
		if(ret!=0)flag=0;//能不能达到这个票数
		for(int j=2;j<=m;j++)
		{
				if(P[j]>=i)
				flag=0;
		}//这个票数能不能获胜
		ans[++cnt2]=pre;	 
	}  
	sort(ans+1,ans+1+cnt2);
	LL answer=ans[1];
	for(int i=2;i<=cnt2;i++)
	{
		answer=min(answer,ans[i]);
	}
	
	cout<<answer<<endl;

} 

接下来的大神的代码精简于我的代码两三倍。

详细的请见这里

他主要存储的思路是存储每个人的价值和对应党派,这样写出来会比我少用很多变量和数组。

尤其是中间从后往前的一步是核心的操作。对应枚举的票数,大于票数的一定要贿赂,先把贵的作为对应党派的票数,往后一旦超过了枚举的票数,就贿赂,这样能保证在不大于票数的情况下贿赂的都是尽可能小的。


#include<bits/stdc++.h>

using namespace std;

struct Node{

int p,c;

bool operator<(const Node&a)const {

return c<a.c;

}

}node[3005];

int x[3005];

int vis[3005];

int main(){

    int n,m;

    cin>>n>>m;

    for(int i=0;i<n;i++)scanf("%d%d",&node[i].p,&node[i].c);

 sort(node,node+n);//花费从小到大排

 long long ans=1e14;

 for(int i=0;i<n;i++){//枚举得票数位i+1

    memset(x,0,sizeof(x));//x[p]表示p党的票数

   memset(vis,0,sizeof(vis));//vis[i]是否改变了i的意愿

    long long sum=0;

    for(int j=n-1;j>=0;j--){

        if(x[node[j].p]<i||node[j].p==1){  // 从后往前 如果得票数小于i 则不用管 当大于等于i时就需要贿赂了,因为从大玩小枚举的,可以保证花费最小

            x[node[j].p]++;

        }

        else {

            sum+=node[j].c;vis[j]=1;//大于等于i时需要贿赂 更新sum 和x[1]

            x[1]++;

        }

    }

    for(int j=0;j<n;j++){//如果解决了所有大于等于i票数的党使其票数小于i时 x[1]仍然不够i则继续找最便宜的贿赂

        if(x[1]<=i&&!vis[j]&&node[j].p!=1){

            sum+=node[j].c;

            x[1]++;

           // vis[j]=1;

        }

    }

    if(x[1]>i)ans=min(ans,sum);//找到成立的最优解

 

 }

 cout<<ans<<endl;

 

 

}

代码如此精简,完全是因为两步贪心的操作太过完美。

第一步贪心,从后往前去掉了大于票数的。【从后往前,保证去掉大于票数的最后都是较小的】

第二步贪心,补掉了枚举票数的差值。【补差值,可能中途就补完了,所以从小的开始,从前往后】

D. The hat

题意:交互题,简单来说就是一串数字给了n个人,他们围成了一个圆,相邻的人得到的数字只相差1,问在60个询问内能否得到一个人和对面的人的数字相同(只有i和i+n/2算对面)

参考

https://blog.csdn.net/kzn2683331518/article/details/81605059

https://blog.csdn.net/kzn2683331518/article/details/81605059

事实上我们只用考虑1~n/2的一边就可以了,对于di=a[i]-a[i+n/2],显然d[i]和d[i+1]差值为0、-2、2.

又根据题意,n是偶数,且假设n=4k+2,每个人和对面的人都只会相差2k+1,而2k+1是奇数,对于奇数个+1、-1最后得到的数不可能等于原来的数,所以如果有答案一定是n=4k。

我们考虑答案也就是d[i]=0,所有d[i]的图像一定就是一条过x轴的图像。

又因为di的差值时连续的,而对于4k的情况:ai->a[i+n/2]相差2k个+-1,相差一定是偶数,也就是di也一定是偶数。(ai和对面的a[i+n/2]同奇偶),也就是可以存在到0点的情况,也就可以看做是图像连续地过x轴。

那么我们只需要求一个答案,找到任意一个端点di相异的区间即可,因为在x轴上的di点所在对应的小部分直线,要么是在

x轴上的,要么就是过x轴的,不管怎么样,二分都可以求得答案。

为了这样求得答案,我们需要保证一开始左右区间也是相异的,那么又由于di和d[i+n/2]是相异的。保证了相异之后就保证了一定有解。

就取1和1+n/2作为左右端点开始二分处理。

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<map>
#include<vector>
#define LL long long
using namespace std;

int n; 
int x,y,a,b;
LL L,R,MID;
int main()
{
   cin>>n;
   if(n%4)
   {
   	 cout<<"! -1"<<endl;
   	 return 0;
   }
   cout<<"? 1"<<endl;
   cout<<"? "<<1+n/2<<endl;
   cin>>x>>y;
   if(x==y)
   {
   	 cout<<"! 1"<<endl;
   	 return 0;
   }
   L= x-y; 
   R= y-x;
   int l=1,r=1+n/2;
   while(l<=r)
   {
   	  int m=(l+r)/2;
   	  cout<<"? "<<m<<endl;
   	  cin>>a;
   	  cout<<"? "<<m+n/2<<endl;
   	  cin>>b;
   	  MID=a-b;
      if(a==b)
   	  {
  	   	cout<<"! "<<m<<endl;
  	   	return 0;
      }
      else if(MID*L<0) r=m;
      else l=m+1;
   }
   cout<<"! -1"<<endl;
} 

E. Sergey's problem

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值