2016-2017 ACM-ICPC, NEERC, Southern Subregional Contest (Online Mirror, ACM-ICPC Rules, Teams Preferred)
话说这场比赛就是被小C叫过来水了一场啊...
然后就和机房里的人组了个队(一人rating撑起一片天).
然后就只写了7道题,虽然我只写了4道水题(I题没写出来真是可惜),并且WA了无数次.
但是至少A题我也提出了一点想法(虽然没什么用),至少有一点贡献.
比赛进程
比赛一开始,随机点开E题,然后读了读题目,感觉没什么,以为就是1到n一个个解锁过来,然后就以为是一道模拟题.
然而第二个样例并不能过,所以又重新读了读题,发现解锁的顺序是不确定的.
于是发现是一片新大陆…根本不会做.
然后发现好像对于两个队伍A,B,只有A先或者B先,而这两种对答案的贡献都是能算的,于是先算出来想dp一下,然后YY了一下,发现好像只要取大的就行了,应该没有冲突吧…
抱着沉重的心交了一发,于是就A掉了(获得成就:E题一血).此时已过半个小时.
看H题人蛮多,点开看看,好像不难,码了一会,过了样例就交了一发(打开疯狂提交的序幕),无情WA在了27组数据,原因是并没有看到只能够删除选定的文件,但是当时并没有发现,以为是不用理长度,所以去掉后又WA了一发,这次在第5组,那么就一定是我想错了吧,这时我依旧没有发现那个错误,已经快一个小时了.
再过一会,发现了,然后以为全问号是不行的,那么WA在了55组.
最后艰难地还是过了,WA了3次(居然还不是最多的).此时已过一个小时,同时队友也A掉了G题(WA了两次).
成功三题最后…
然后点开J题,刚刚看到一个物品有两个属性,队友点开了B题,问我这是什么东西,我看了看,交互题,”好吧,这题给我”,然后让他去写J题.
发现B题水的不行-_-,就和队友一起吃饭去了.
吃饭回来,已过一个半小时,一下子就码出了B题代码,交了一发,然后就见到了从未见到过的问题:
然后调了十来分钟,才发现是格式出错了,果然还是上一道交互题做的时间间隔太远了,都已经忘了怎么写交互题了…
这时过去了近两个小时.
然后就开始集全队之力开始攻克A题,因为这时已经有100+人过A题了而我们却还没过.
好,想着贪心却根本不知道如何来贪心,所以就很尴尬.
然后就出现了一个思路:
先拿最大的两个,如果次大的已经是最小的,那么看前面的选择中有没有可以放进这个最大的,如果可以就放进去,不然就把答案减一,这样就能够把最大的那个继续减下去了.
那么当我们把代码写出来后,发现RE了,检查一下数组,发现开小了囧…开大后又发现输出了一个全零的方案于是又WA了一遍,然后才A掉,现在已过三小时.
终于六题不是最后的了...
于是又分工,我打开了C题,他们开始一起搞I题(然而最后都没有写出来).
发现C题不是很难,对于所有的询问都BFS一遍,用堆来存买哪些,当消费小于有的钱时就成功了,不然返回-1.
然后这道题发生了很多很多尴尬的事,都不好意思说了…
首先没有看见有多少的需求,就一个个放进去,然后WA了很久,发现之后又WA了,发现有些事情没有处理好(可以无限买某种东西),于是又WA了无数遍…
WA了五次后终于A了,成功七题倒数(还不是我错的太多),分数从此定格,再也没有变过了…此时已过四个小时.
然后所有人就都在看I了,期间看了一下D,感觉不太像是能够写的,所以果断放弃.
开始各种脑补贪心,于是就走进了歪路,最后也没走出来(话说贪心好像能写),开始各种尝试,最后发现自己随手出的样例都能把自己程序卡掉后就很懵逼.
时间渐渐过去,却只能看自己的排名一个个的往下掉,很不爽的好吗!
头脑风暴了一个小时,最后还是没有写出来,最后91名结束.
最后发现I题其实用最小费用流就能够写了,还是学的太少了,还是Too Young Too Simple.
Solution:
虽然我们队A了7题(赛后我又把I题A了),但是我知道怎么写的就只有5道,所以就写一下吧.
A:Toda 2
Task:
给你一个序列,一次操作可以把2到5个数同时减一,一个数不能够被减到负数,但是可以被使用,最后使所有数都相同.问最后相同的数最大是多少,以及输出一种方案.
Solution:
首先有一个贪心的想法,每次都把两个最大的拿出来减1,同时固定一个下界,让数字不能够被减到这个数字以下.
那么当最大的两个都等于答案时就能够结束了,如果次大的等于答案,那么就在之前的方案中插入现在的最大值,如果不能够插进去,那么就让答案减1,让那个最大的值能够减小.
然后这道题就是模拟一下就行了.
#include<cstdio>
#include<string>
#include<queue>
#include<cstring>
#include<iostream>
#include<algorithm>
#define M 105
using namespace std;
int n,sum=0,ans=0;
struct node{
int v,id;
bool operator <(const node &a)const{
return v<a.v;
}
}A[M];
char str[10005][M];
priority_queue<node>Q;
int main(){
scanf("%d",&n);
int mi=M;
for(int i=1;i<=n;i++){
scanf("%d",&A[i].v),A[i].id=i-1;
mi=min(mi,A[i].v);
Q.push(A[i]);
}
int t=0;
ans=mi;
while(!Q.empty()){
t++;
node a=Q.top();
Q.pop();
node b=Q.top();
Q.pop();
if(ans<0)ans=0;
if(a.v==ans&&b.v==ans)break;
if(ans==0||(a.v>ans&&b.v>ans)){
if(a.v)a.v--;
if(b.v)b.v--;
str[t][a.id]=str[t][b.id]='1';
Q.push(a);
Q.push(b);
}else if(a.v>ans&&b.v==ans){
int f=0;
for(int i=1;i<t&&!f;i++)
if(str[i][a.id]!='1'){
f=1,str[i][a.id]='1';
if(a.v)a.v--;
t--;
}
if(!f)ans--,t--;
Q.push(a);
Q.push(b);
}
}
t--;
for(int i=1;i<=t;i++){
for(int j=0;j<n;j++)
if(str[i][j]!='1')str[i][j]='0';
}
printf("%d\n%d\n",ans,t);
for(int i=1;i<=t;i++)puts(str[i]);
return 0;
}
B:Minimum and Maximum
Task:
在 [3∗n2]−2 次询问中得到一个长度为n的序列的最大值和最小值,每次询问能够知道序列中两数的大小关系.
Solution:
还是一道水题吧,只用每次两两比较,大的加入大的里面,小的加入小的里面,然后依次两两比较就能够得到最大和最小了.
但是因为格式不对错了一次很不开心-_-,虽然没有扣分,但是就是不舒服…
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define M 55
using namespace std;
int winer[M],loser[M];
int main(){
int cas;
scanf("%d",&cas);
while(cas--){
int n;scanf("%d",&n);
if(n==1){
printf("! 1 1\n");
fflush(stdout);
continue;
}
int wsz=0,lsz=0;
for(int i=1;i+1<=n;i+=2){
printf("? %d %d\n",i,i+1);
fflush(stdout);
char S[5];scanf("%s",S);
if(S[0]=='>'){
winer[wsz++]=i;
loser[lsz++]=i+1;
}else {
winer[wsz++]=i+1;
loser[lsz++]=i;
}
}
if(n&1){
printf("? %d %d\n",loser[lsz-1],n);
fflush(stdout);
char S[5];scanf("%s",S);
if(S[0]=='>'){
loser[lsz-1]=n;
}else if(S[0]=='<'){
winer[wsz++]=n;
}
}
int ans1,ans2;//ans1 small ans2 big
while(wsz>1){
int nsz=0;
for(int i=0;i+1<wsz;i+=2){
printf("? %d %d\n",winer[i],winer[i+1]);
fflush(stdout);
char S[5];scanf("%s",S);
if(S[0]=='>'){
winer[nsz++]=winer[i];
}else winer[nsz++]=winer[i+1];
}
if(wsz&1){
printf("? %d %d\n",winer[nsz-1],winer[wsz-1]);
fflush(stdout);
char S[5];scanf("%s",S);
if(S[0]=='<')winer[nsz-1]=winer[wsz-1];
}
wsz=nsz;
}
ans2=winer[0];
while(lsz>1){
int nsz=0;
for(int i=0;i+1<lsz;i+=2){
printf("? %d %d\n",loser[i],loser[i+1]);
fflush(stdout);
char S[5];scanf("%s",S);
if(S[0]=='<'){
loser[nsz++]=loser[i];
}else loser[nsz++]=loser[i+1];
}
if(lsz&1){
printf("? %d %d\n",loser[nsz-1],loser[lsz-1]);
fflush(stdout);
char S[5];scanf("%s",S);
if(S[0]=='>')loser[nsz-1]=loser[lsz-1];
}
lsz=nsz;
}
ans1=loser[0];
printf("! %d %d\n",ans1,ans2);
fflush(stdout);
}
return 0;
}
C:Bulmart
Task:
给出一张图,有些点可以卖出一些东西,每个东西都有它自己的价格,现在给出q个询问,每个询问有st,ned,mone三个属性,表示你在st点,需要ned个物品,有money的钱,现在需要在每个城市订购物品,没有运费的存在,所以再远也没有关系,只要你付得起买东西的钱就行了,现在问最远的一个商店最近在哪里.
Solution:
说实话想到BFS就算了一下复杂度,发现没超,于是就开始敲了,然后就没看清题目数据范围,于是悲剧就开始了…
其实只要一直选现在最便宜的ned个物品,用堆来维护一下就可以了,当满足时就能够结束了.
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<queue>
#include<vector>
#define M 5005
#define LL long long
using namespace std;
void Rd(int &res){
res=0;char p;
while(p=getchar(),p<'0');
do{
res=(res<<1)+(res<<3)+(p^48);
}while(p=getchar(),p>='0');
}
struct W{
int v,cnt;
bool operator <(const W &a)const{
return v<a.v;
}
};
vector<W>Q[M];
vector<int>edge[M];
int q[2][M],sz[2];
bool mark[M];
int query(int St,int Ned,int Mon){
priority_queue<W>qq;
LL cnt=0,val=0;
memset(mark,0,sizeof(mark));
sz[0]=sz[1]=0;
int cu=0,nx=1,T=0;
q[0][sz[cu]++]=St;
mark[St]=1;
while(sz[cu]){
sz[nx]=0;
for(int i=0;i<sz[cu];i++){
int x=q[cu][i];
int upp=Q[x].size();
for(int l=0;l<upp;l++){
W now=Q[x][l];
if(cnt<Ned){
W in;
in.v=now.v;
in.cnt=min(1LL*now.cnt,Ned-cnt);
now.cnt-=in.cnt;
val+=1LL*in.cnt*in.v;
qq.push(in);
cnt+=in.cnt;
}
while(now.cnt){
W tp=qq.top();
if(tp.v>now.v){
if(tp.cnt<=now.cnt){
now.cnt-=tp.cnt;
val=val-1LL*tp.cnt*tp.v+1LL*now.v*tp.cnt;
qq.pop();
tp.v=now.v;
qq.push(tp);
}else {
qq.pop();
W in;
in.v=now.v,in.cnt=now.cnt;
qq.push(in);
tp.cnt-=now.cnt;
qq.push(tp);
val=val-1LL*now.cnt*tp.v+1LL*now.cnt*now.v;
now.cnt=0;
}
}else break;
}
}
if(cnt==Ned&&val<=Mon)return T;
int up=edge[x].size();
for(int j=0;j<up;j++){
int to=edge[x][j];
if(!mark[to]){
mark[to]=1;
q[nx][sz[nx]++]=to;
}
}
}
cu=!cu,nx=!nx,T++;
}
return -1;
}
int main(){
int n,m;
Rd(n),Rd(m);
for(int i=1;i<=m;i++){
int x,y;Rd(x),Rd(y);
edge[x].push_back(y);
edge[y].push_back(x);
}
int t;Rd(t);
for(int i=1;i<=t;i++){
int x,y,z;
Rd(x),Rd(y),Rd(z);
Q[x].push_back((W){z,y});
}
int qu;Rd(qu);
while(qu--){
int x,y,z;
Rd(x),Rd(y),Rd(z);
printf("%d\n",query(x,y,z));
}
return 0;
}
E:Award Ceremony
Task:
给你一组分数,以及解锁后该分数的改变大小.问怎样改变解锁分数的顺序,能够使得过程中所有人排名的变化量最大.
Solution:
我们先考虑两两的比较先后,我们可以得到这两个分数先后解锁的贡献,然后取最大值加入答案中就行了,说实话我也不知道为什么,应该就是不会成环.
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define M 105
using namespace std;
struct W{
int id,v,d;
bool operator <(const W &a)const{
if(v!=a.v)return v>a.v;
return id<a.id;
}
}Q[M];
int val[M][M];
int main(){
int n;
scanf("%d",&n);
for(int i=1;i<=n;i++)scanf("%d %d",&Q[i].v,&Q[i].d),Q[i].id=i;
int ans=0;
for(int i=1;i<=n;i++)
for(int j=i+1;j<=n;j++){
W a=Q[i],b=Q[j],c=Q[i],d=Q[j];
c.v+=c.d,d.v+=d.d;
if(a<b&&b<c||b<a&&c<b)val[i][j]++;
if(c<b&&d<c||b<c&&c<d)val[i][j]++;
if(a<b&&d<a||b<a&&a<d)val[j][i]++;
if(d<a&&c<d||a<d&&d<c)val[j][i]++;
ans+=max(val[i][j],val[j][i]);
}
printf("%d\n",ans);
return 0;
}
I:Olympiad in Programming and Sports
Task:
给你两个序列,从第一个序列中取s个数,第二个序列中取q个数,但是两个序列中的数的位置不能够相同,求所有取出的数的和的最大值.
Solution:
构图跑一下最小费用流就行了,从源点向每个点连一条流量为1的边,然后再从那些点分别连两条边到两个点,流量都为1,然后边权为上下两个序列的值的相反数,最后再从这两个点连流量分别为s和q的边到汇点就行了.
这样的题都写不出来,太水了TAT…
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<queue>
#include<vector>
#define M 3005
#define INF 1000000007
using namespace std;
struct W{
int to,cap,co,id;
}tmp;
vector<W>edge[M];
void Add(int x,int y,int t,int v){
edge[x].push_back((W){y,t,v,edge[y].size()});
edge[y].push_back((W){x,0,-v,edge[x].size()-1});
}
int a[M],b[M],st,ed,dis[M];
int prev[M],nam[M];
bool mark[M];
bool SPFA(){
queue<int>q;
for(int i=0;i<=ed;i++)dis[i]=INF;
dis[st]=0;
mark[st]=1;
q.push(st);
while(!q.empty()){
int now=q.front();q.pop();
// printf("now:%d\n",now);
mark[now]=0;
int up=edge[now].size();
for(int i=0;i<up;i++){
W &op=edge[now][i];
int ll=dis[now]+op.co,&to=op.to;
// printf("to:%d %d %d\n",to,ll,dis[to]);
if(op.cap>0&&ll<dis[to]){
prev[to]=now;
nam[to]=i;
dis[to]=ll;
if(!mark[to]){
q.push(to);
mark[to]=1;
}
}
}
}
return dis[ed]!=INF;
}
int MFMC(){
int re=0;
while(SPFA()){
// puts("_-");
int max_flow=INF;
for(int i=ed;i!=st;i=prev[i]){
int &pl=edge[prev[i]][nam[i]].cap;
if(pl<max_flow)max_flow=pl;
}
re+=1LL*dis[ed]*max_flow;
for(int i=ed;i!=st;i=prev[i]){
W &op=edge[prev[i]][nam[i]];
op.cap-=max_flow;
edge[op.to][op.id].cap+=max_flow;
}
}
return re;
}
int main(){
int n,s,p;
scanf("%d %d %d",&n,&s,&p);
for(int i=1;i<=n;i++)scanf("%d",&a[i]);
for(int i=1;i<=n;i++)scanf("%d",&b[i]);
st=0,ed=n+3;
Add(n+1,ed,s,0);
Add(n+2,ed,p,0);
for(int i=1;i<=n;i++)Add(st,i,1,0),Add(i,n+1,1,-a[i]),Add(i,n+2,1,-b[i]);
printf("%d\n",-MFMC());
int up=edge[n+1].size();
for(int i=0;i<up;i++){
W &op=edge[n+1][i];
int to=op.to;
if(to==n+3)continue;
if(op.cap==1)printf("%d ",to);
}putchar('\n');
for(int i=0;i<up;i++){
W op=edge[n+2][i];
int to=op.to;
if(to==n+3)continue;
if(op.cap==1)printf("%d ",to);
}putchar('\n');
return 0;
}