E:Stas and the Queue at the Buffet
题目链接:https://vjudge.net/contest/297959#problem/E
题目大意:n个学生,第i个学生的初始位置为i。每个学生有两个参数a,b,第i个学生的不满意值为
ai⋅(i−1)+bi⋅(n−i)。现在我们需要对这些学生重新排列,使得所有学生的不满意值的和最小。
题解:每个学生的不满意值为:sumi=ai*(i−1)+bi*(n−i)=(ai-bi)*i-ai+n*bi
两边求和为:
很明显:为定值,我们要求ans的最小值,也就是要使 最小。因为i是逐渐增加的,我们要使乘积和最小,也就是要使 从大到小排列即可。
代码:
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <queue>
#include <cmath>
#include <cstring>
#include <string>
#include <vector>
#include <set>
#include <stack>
#include <list>
#include <map>
#define P(x) x>0?x:0
#define INF 0x3f3f3f3f
using namespace std;
typedef long long ll;
const int maxn=100005;
ll n;
ll ans;
struct node
{
ll a,b,sum;
node(ll x=0,ll y=0,ll z=0):a(x),b(y),sum(z){}
}aa[maxn];
bool cmp(node n1,node n2)
{
return (n1.a-n1.b)>(n2.a-n2.b);
}
int main()
{
while(~scanf("%lld",&n))
{
ans=0;
for(ll i=1;i<=n;i++)
{
scanf("%lld%lld",&aa[i].a,&aa[i].b);
}
sort(aa+1,aa+n+1,cmp);
for(ll i=1;i<=n;i++)
{
aa[i].sum=(aa[i].a*(i-1)+aa[i].b*(n-i));
ans+=aa[i].sum;
}
printf("%lld\n",ans);
}
return 0;
}
B:Median String
题目链接:https://vjudge.net/contest/297959#problem/B
题目大意:k长度的两个字符串s和t(保证s的字典序小于t)。字典序在两个字符串中间的字符串中(保证数量是奇数),最中间的那个字符串是什么,输出。
题解:将输入的字符串看成26进制的一个大数,模拟大数相加,相除,得到最终的结果。
代码:
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <queue>
#include <cmath>
#include <cstring>
#include <string>
#include <vector>
#include <set>
#include <stack>
#include <list>
#include <map>
#define P(x) x>0?x:0
#define INF 0x3f3f3f3f
using namespace std;
typedef long long ll;
const int maxn=200005;
char s[maxn],t[maxn],sum[maxn],ans[maxn];
int k;
void median()
{
//add
for(int i=k-1;i>=0;i--)
{
sum[k-1-i]=s[i]-'a'+t[i]-'a';
}
for(int i=0;i<=k;i++)
{
if(sum[i]>25)
{
int quo=sum[i]/26;
sum[i]-=26*quo;
sum[i+1]+=quo;
}
}
//divide ans/2
for(int i=k;i>=0;i--)//sum中高位在后,低位在前
{
int mod=sum[i]%2;
ans[i]=sum[i]/2;
if(mod)
sum[i-1]+=mod*26;
}
for(int i=k-1;i>=0;i--)
{
ans[i]+='a';
}
}
int main()
{
while(~scanf("%d",&k))
{
getchar();
scanf("%s",s);
scanf("%s",t);
memset(ans,0, sizeof(ans));
median();
for(int i=k-1;i>=0;i--)//ans高位在后,低位在前
{
printf("%c",ans[i]);
}
printf("\n");
}
return 0;
}
A:Dima and a Bad XOR
题目链接:https://vjudge.net/contest/297959#problem/A
题目大意:有一个矩阵(n*m),矩阵元素是非负数。我们要从每一行中找出一个数,使得这n个数异或不为0,输出TAK以及选择的每行的数的位置(可能答案不唯一)。如果无解则输出NIE。
题解:将每行的数插入set(内部自动有序且不含重复元素的容器)中,我们取*st[i].begin()异或,如果非0,有解,输出对应位置。如果为0,我们就遍历一遍st[i].size,如果有大于1的,那就是有解,输出对应位置。如果所有行都只有一个不同数,则无解。
其实就是有两种情况:
第一种:每行都只有一个不同数,如果异或为0,则无解;否则有解。
第二种:存在有两个或两个以上的不同数的行。现在我们假设有且只有一行Rx,存在两个不同数Num1,Num2。并且我们仍然按照前边说的将set中的第一个元素异或,结果为0。这个时候我们假设除了Rx的所有行的异或结果为X,X^Num1=0,那么X=Num1,并且我们知道Num1!=Num2,因此X^Num2!=0。
因此,我们很容易可以知道,第二种情况中,只要存在一行有两个或两个以上的不同数,那么题目就是肯定有解的(当然多行也肯定有解啦)。
代码:
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <queue>
#include <cmath>
#include <cstring>
#include <string>
#include <vector>
#include <set>
#include <stack>
#include <list>
#include <map>
#define P(x) x>0?x:0
#define INF 0x3f3f3f3f
using namespace std;
typedef long long ll;
const int maxn=505;
int n,m;
int a[maxn][maxn];
int ans[maxn];
set<int> st[maxn];
void init()
{
for(int i=0;i<n;i++)
{
st[i].clear();
}
}
void pos()
{
for(int i=0;i<n;i++)
{
for(int j=0;j<m;j++)
{
if(a[i][j]==*st[i].begin())
ans[i]=j+1;
}
}
}
int main()
{
while(~scanf("%d%d",&n,&m))
{
init();
for(int i=0;i<n;i++)
{
for(int j=0;j<m;j++)
{
scanf("%d",&a[i][j]);
st[i].insert(a[i][j]);
}
}
int temp=*st[0].begin();
for(int i=1;i<n;i++)
{
temp=temp^(*st[i].begin());
}
if(temp)
{
printf("TAK\n");
pos();
for(int i=0;i<n;i++)
{
printf("%d%c",ans[i]," \n"[i==n-1]);
}
}
else
{
for(int i=0;i<n;i++)
{
if(st[i].size()>1)
{
st[i].erase(*st[i].begin());
temp=temp^(*st[i].begin());
break;
}
}
if(temp)
{
printf("TAK\n");
pos();
for(int i=0;i<n;i++)
{
printf("%d%c",ans[i]," \n"[i==n-1]);
}
}
else
{
printf("NIE\n");
}
}
}
return 0;
}
C:Equalize Them All
题目链接:https://vjudge.net/contest/297959#problem/C
题目大意:一个有n个元素的数组,我们可以进行 【1】ai=ai+|ai−aj|,【2】ai=ai−|ai−aj|这两种操作,其中|i−j|=1。问我们通过最少多少次操作使得数组的每个元素值相等。
题解:最优解肯定是将数组的所有元素转换为众数mode。首先我们找到众数mode,以及任意一个众数的位置pos_mode。我们从pos_mode往两边遍历,如果比mode小则进行操作【1】使得小数转换为众数,并更新pos_mode;如果比mode大,则进行操作【2】使得大数转换为众数,并更新pos_mode;如果相等,则直接更新pos_mode。中间用ans来记录操作的次数,用结构体anser数组来记录每次操作,最后输出即可。
在找众数的时候,for循环的i是mark的下标,然后我写的<=maxn,所以RE了(QAQ)两次。
代码:
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <queue>
#include <cmath>
#include <cstring>
#include <string>
#include <vector>
#include <set>
#include <stack>
#include <list>
#include <map>
#define P(x) x>0?x:0
#define First(i,j) a[i]=a[i]+P(a[i]-a[j])
#define Second(i,j) a[i]=a[i]-P(a[i]-a[j])
#define INF 0x3f3f3f3f
using namespace std;
typedef long long ll;
const int maxn=200005;
ll a[maxn];
ll mark[maxn];
ll n;
ll mode,pos_mode;
ll pos_front,pos_back;
ll ans;
struct node{
ll key,x,y;
node(ll aa=0,ll bb=0,ll cc=0):key(aa), x(bb),y(cc){}
}anser[maxn];
int main()
{
while(~scanf("%lld",&n))
{
ans=0;
memset(mark,0,sizeof(mark));
for(ll i=1;i<=n;i++)
{
scanf("%lld",&a[i]);
mark[a[i]]++;
}
ll max_=-1;
ll flag=0;
for(ll i=1;i<=n;i++)
{
if(mark[a[i]]==n)
{
printf("0\n");
flag=1;
break;
}
if(mark[a[i]]>max_)
{
max_=mark[a[i]];
mode=a[i];
pos_mode=i;
}
}
if(flag)
continue;
pos_front=pos_mode-1;
pos_back=pos_mode+1;
for(ll i=pos_front;i>=1;i--)
{
if(a[i]>mode)
{
Second(i,pos_mode);
anser[ans]=node(2,i,pos_mode);
ans++;
}
else if(a[i]<mode)
{
First(i,pos_mode);
anser[ans]=node(1,i,pos_mode);
ans++;
}
pos_mode=i;
}
pos_mode=pos_back-1;
for(ll i=pos_back;i<=n;i++)
{
if(a[i]>mode)
{
Second(i,pos_mode);
anser[ans]=node(2,i,pos_mode);
ans++;
}
else if(a[i]<mode)
{
First(i,pos_mode);
anser[ans]=node(1,i,pos_mode);
ans++;
}
pos_mode=i;
}
printf("%lld\n",ans);
for(ll i=0;i<ans;i++)
{
printf("%lld %lld %lld\n",anser[i].key,anser[i].x,anser[i].y);
}
}
return 0;
}
D:Superhero Battle
题目链接:https://vjudge.net/contest/297959#problem/D
题目大意:英雄打怪。怪兽有H个生命值,每一回合有n分钟,每分钟怪兽会受到不同的伤害d[i](可正可负),回合是依次循环的。问多少分钟后怪兽被kill,如果不能则输出-1。
题解:我们将一个回合的伤害前缀和存起来,存到sum数组中,那么sum[[n]则是一个整回合的净伤害。然后记录最大的伤害值为min_,并且记录最大伤害值出现在每个回合的时间pos_min。假设当前生命值的状态是hp0,hp1,hp2,hp3,...,hpn。
hp0=H
hp1=hp0+sum[n]
hp2=hp0+2*sum[n]
hp3=hp0+3*sum[n]
……
hpn=hp0+n*sum[n]
由此推得hpk=hp0+k*sum[n]
我们可以很容易理解,当hpi<= - min_时,怪兽就会在第i+1轮中死掉,最多活到i+1轮的pos_min时。
因此我们可以得到:第k轮hpk=hp0+k*sum[n]<= - min_时,怪兽将会在k+1轮中死掉。
hpk=hp0+k*sum[n]<= - min_
k>= (- min_ - hp0) / sum[n]
当然这里的话,sum[n]是负数,所以我们可以得到:
k>= (min_ + hp0) / abs(sum[n])
这里k要向上取整。我们可以这样想:在第k轮的时候hpk<=min_才能使怪兽在k+1轮死掉。如果我们的k向下取整,那么此时取整后的第k轮不能有hpk<=min_,所以k要向上取整。
当然,这上面的情况不包括特殊情况。
1.永远死不掉:(1).最大的伤害min_>=0;(2).最大的伤害-H<min_<0(也就是第一轮死不掉),并且sum[n]>=0(也就是每轮的净伤害为正)
2.第一轮死掉:最大的伤害值min_<=-H并且sum[n]>=0
尤其是第二种情况不易想到吧,如果不特判,sum[n]可能为0,就会出现RE。
代码:
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <queue>
#include <cmath>
#include <cstring>
#include <string>
#include <vector>
#include <set>
#include <stack>
#include <list>
#include <map>
#define P(x) x>0?x:0
#define INF 0x3f3f3f3f
using namespace std;
typedef long long ll;
const int maxn=2e5+5;
ll H,n;
ll d[maxn];
ll sum[maxn];
ll ans;
ll pos_min;
ll min_;
int main()
{
while(~scanf("%lld%lld",&H,&n))
{
memset(sum,0, sizeof(sum));
min_=INF;
for(ll i=1;i<=n;i++)
{
scanf("%lld",&d[i]);
sum[i]=sum[i-1]+d[i];
if(sum[i]<min_)
{
min_=sum[i];
pos_min=i;
}
}
if(min_>=0||(min_<0 && abs(min_)<H && sum[n]>=0))//不能打死的情况
{
printf("-1\n");
continue;
}
//在第一轮打死
else if(sum[n]>=0)
{
ans=0;
for(ll i=1;i<=n;i++)
{
if(H+sum[i]<=0)
{
ans+=i;
printf("%lld\n",ans);
break;
}
}
}
else
{
ll quo=P((H + min_)/abs(sum[n]));
if(P((H+min_)%abs(sum[n]))>0)
{
quo++;
}
ans=quo*n;
ll rem=H+quo*sum[n];
for(ll i=1;i<=n;i++)
{
if(rem+sum[i]<=0)
{
ans+=i;
printf("%lld\n",ans);
break;
}
}
}
}
return 0;
}