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