2017 ACM/ICPC Asia Regional Shenyang Online
写一些题解,完全不想总结。唯一的感想就是开黑打都被虐。。主账号被我智障似的T了一发,lzq手误把一份AC代码交了两个账号。。A题卡太久。。。
听某化工大教练说我校rank 87,应该有名额吧。。
本场我写了两个题外加一个助攻,终榜过了6道题,堪忧。
按题号来看看这6个题以及刚补的1010吧:
1001 string string string
开场三秒给队友说了题意,求有多少个字串恰好出现k次。后缀数组基本忘完了,交给他们了。AC时间:03:51
献上大佬代码:
#include<bits/stdc++.h>
using namespace std;
#define LL long long
using namespace std;
const int N=2e5+10;
int x[N*3];
int AC[N*3],AB[N*3],ABC[N*3],AD[N*3];
int NC(int *r,int a,int b,int l)
{
return r[a]==r[b]&&r[a+l]==r[b+l];
}
void DA(int *r,int *sa,int n,int m)
{
int i,j,p,*x=AC,*y=AB,*t;
for(i=0; i<m; i++) ABC[i]=0;
for(i=0; i<n; i++) ABC[x[i]=r[i]]++;
for(i=1; i<m; i++) ABC[i]+=ABC[i-1];
for(i=n-1; i>=0; i--) sa[--ABC[x[i]]]=i;
for(j=1,p=1; p<n; j*=2,m=p)
{
for(p=0,i=n-j; i<n; i++) y[p++]=i;
for(i=0; i<n; i++) if(sa[i]>=j) y[p++]=sa[i]-j;
for(i=0; i<n; i++) AD[i]=x[y[i]];
for(i=0; i<m; i++) ABC[i]=0;
for(i=0; i<n; i++) ABC[AD[i]]++;
for(i=1; i<m; i++) ABC[i]+=ABC[i-1];
for(i=n-1; i>=0; i--) sa[--ABC[AD[i]]]=y[i];
for(t=x,x=y,y=t,p=1,x[sa[0]]=0,i=1; i<n; i++)
x[sa[i]]=NC(y,sa[i-1],sa[i],j)?p-1:p++;
}
return ;
}
int h[N*3];
int Rank[N*3];
void get_height(int *r,int *sa,int n)
{
int k=0,j;
for(int i=1; i<=n; i++) Rank[sa[i]]=i;
for(int i=0; i<n; h[Rank[i++]]=k)
for(k?k--:0,j=sa[Rank[i]-1]; r[i+k]==r[j+k]; k++) ;
return ;
}
int AS[N*3],r[N*3];
LL s[N][2];
char str[N];
struct node
{
int h,d,mi;
node(int _h,int _d,int _mi)
{
h=_h,mi=_mi,d=_d;
}
};
int AAC(int n,int k)
{
stack<node>zh;
zh.push(node(0,1,0));
LL res=0;
h[n+1]=0;
for(int i=2; i<=n+1; i++)
{
int x=h[i];
if(x>zh.top().h) zh.push(node(x,1,x));
else
{
int ti=0;
node now(x,1,x);
while(zh.top().h>x)
{
int tmp=zh.top().h;
now.d+=zh.top().d;
now.mi=min(now.mi,zh.top().mi);
ti+=zh.top().d;
zh.pop();
if(ti==k-1)
{
int delta=max(x,zh.top().h);
res+=tmp-max(x,zh.top().h);
}
}
zh.push(now);
}
}
printf("%I64d\n",res);
}
int main()
{
int tt;
scanf("%d",&tt);
while(tt--)
{
int k;
scanf("%d%s",&k,str);
memset(s,0,sizeof(s));
memset(h,0,sizeof(h));
int len=strlen(str);
for(int i=0; i<len; i++) x[i]=str[i];
x[len]=0;
DA(x,AS,len+1,255);
get_height(x,AS,len);
if(k==1)
{
LL ans=0;
for(int i=1; i<=len; i++)
{
int tmp=len-AS[i];
ans+=tmp-max(h[i],h[i+1]);
}
printf("%I64d\n",ans);
}
else AAC(len,k);
}
return 0;
}
1002 cable cable cable
我们第二道出的题,然后被题意卡了很久,lzq大队长首先发现是个shab题,一发过了,然而都已经过了200+个队了。AC时间:0:25再次献上大佬代码:
ll n,m;
while(~scanf("%lld%lld",&n,&m))
{
printf("%lld\n",(n-m+1)*m);
}
1004 array array array
哈哈,终于轮到我出的题了。在此感谢北化工教练带我们联合训练,正好前几天做过一个原题,具体移驾: 2015 ICPC长春-HDU5532。从后往前看题,看懂这题后瞬间明白好像是原题,然后刷了一下榜,果然CQU率先AC,然后跟杨神说是原题,结果换来一张懵逼脸,直接上,把原题的1改成k-AC。0:08
const int N=1e6+10;
int a[N],b[N];
char s[N];
int main()
{
int t,n,kk;
scanf("%d",&t);
while(t--)
{
scanf("%d%d",&n,&kk);
for(int i=0; i<n; i++) scanf("%d",&a[i]);
if(n<=2) puts("A is a magic array.");
else
{
memset(b,0,sizeof(b));
int len=0,f=0;
b[len++]=a[0];
for(int i=1; i<n; i++)
{
if(a[i]>=b[len-1]) b[len++]=a[i];
else
{
int pos=upper_bound(b,b+len,a[i])-b;
b[pos]=a[i];
}
}
if(len>=n-kk) f=1;
len=0;
b[len++]=a[n-1];
for(int i=n-2; i>=0&&!f; i--)
{
if(a[i]>=b[len-1]) b[len++]=a[i];
else
{
int pos=upper_bound(b,b+len,a[i])-b;
b[pos]=a[i];
}
}
if(len>=n-kk) f=1;
if(f) puts("A is a magic array.");
else puts("A is not a magic array.");
}
}
return 0;
}
1005 number number number
本校第4道过的题,学姐太强了。貌似一个递推式然后裸矩阵快速幂,没有去补。献上代码:
#include<bits/stdc++.h>
#define INF 0x3f3f3f3f
#define lson l,m,root<<1
#define rson m+1,r,root<<1|1
typedef long long ll;
using namespace std;
const int MOD=998244353;
const int maxn=2000010;
struct NODE
{
ll m[2][2];
};
NODE CSAL(NODE A,NODE B)
{
NODE C;
memset(C.m,0,sizeof(C.m));
for(int i=0; i<2; i++)
{
for(int j=0; j<2; j++)
{
for(int k=0; k<2; k++)
{
C.m[i][j]=C.m[i][j]+A.m[i][k]*B.m[k][j];
C.m[i][j]%=MOD;
}
}
}
return C;
}
NODE KSM(NODE A,ll p)
{
NODE tmp;
memset(tmp.m,0,sizeof(tmp.m));
for(int i=0; i<2; i++)
tmp.m[i][i]=1;
while(p)
{
if(p&1)
tmp=CSAL(tmp,A);
A=CSAL(A,A);
p>>=1;
}
return tmp;
}
int main()
{
int n;
while(~scanf("%d",&n))
{
NODE tmp,res,TC;
tmp.m[0][0]=1,tmp.m[0][1]=1;
tmp.m[1][0]=1, tmp.m[1][1]=0;
res=KSM(tmp,3+2*n);
TC.m[0][0]=0,TC.m[0][1]=1;
TC.m[1][0]=0,TC.m[1][1]=0;
res=CSAL(TC,res);
cout<<(res.m[0][0]+MOD-1)%MOD<<endl;
}
}
1008 transaction transaction transaction
再次感谢杨神读题,然后给出了思路,求最长路即可,讨论了一下,把点权转化成边权,构造一个出发点0点和各点间路径长为0,两点间路径虽然是双向的,但费用的计算却要反过来,也就是存在负权边,不能用dij求解。dijstra+priority_queue写习惯了,然后一发板子发现样例过不去,发现vis[]过的点不能再次访问,于是改成spfa样例过了,一发结果T了, 发现数组少了个0,石乐志。AC时间:0:57const int N=200000+10;
struct Edge
{
int u,to,next;
ll w;
} e[N*2];
int n,m,tot;
int vis[N],head[N],num[N];
ll d[N],ans,tc[N];
struct node
{
ll c;
int v;
friend bool operator < (node a,node b)
{
return a.c<b.c;
}
};
//priority_queue<node>q;
void init()
{
tot=0;
// while(!q.empty()) q.pop();
memset(head,-1,sizeof(head));
memset(vis,0,sizeof(vis));
}
void add(int u,int v,ll w)
{
e[tot].to=v,e[tot].w=w,e[tot].next=head[u];
head[u]=tot++;
}
void dij()
{
int f=0;
d[0]=ans=0;
for(int i=1; i<=n; i++) d[i]=-1;
queue<int>q;
q.push(0);
while(!q.empty()&&!f)
{
int u=q.front();
q.pop();
vis[u]=0;
for(int i=head[u];i+1;i=e[i].next)
{
int v=e[i].to;
ll w=e[i].w;
if(d[v]<d[u]+w)
{
d[v]=d[u]+w;
// printf("%d %lld\n",v,d[v]);
ans=max(ans,d[v]);
if(!vis[v])
{
vis[v]=1;
q.push(v);
}
}
}
}
// q.push(node {d[0],0});
// while(!q.empty())
// {
// node tmp=q.top();
// q.pop();
// int u=tmp.v;
// if(vis[u]) continue;
// vis[u]=1;
// for(int i=head[u]; i+1; i=e[i].next)
// {
// int v=e[i].to;
// ll w=e[i].w;
// if(d[v]<d[u]+w)
// {
// d[v]=d[u]+w;
// ans=max(ans,d[v]);
printf("%d %lld\n",v,d[v]);
// q.push(node {d[v],v});
// }
// }
// }
printf("%lld\n",ans);
}
int main()
{
int t;
scanf("%d",&t);
while(t--)
{
init();
scanf("%d",&n);
for(int i=1; i<=n; i++)
{
scanf("%lld",&tc[i]);
add(0,i,0);
}
for(int i=1; i<n; i++)
{
int u,v,w;
scanf("%d%d%d",&u,&v,&w);
add(u,v,tc[v]-tc[u]-w);
add(v,u,tc[u]-tc[v]-w);
}
dij();
}
}
四个题,发现排名在150左右。于是转攻最后一题,01题队友一直在讨论,string不是我们的强项。
1010 ping ping ping(补)
做完第五题只差队友在写的01了,还剩两三个小时左右,此时06和10是最可能做的,06过的人多,讨论了一下完全没想法。杨神给出这道题的题意,树上操作,很有意思。然而我们一堆人陷在最小覆盖和匹配问题上无法自拔。
题意:给你一棵树,然后q条链,每条链由两个点表示这条链不连通,求树上最少坏了几个点。
为什么会想到最小覆盖上去:这很好理解,用最少的点覆盖最多的路径,然而这是路径并非边。学姐给出了把链转化成区间然后贪心思想求最少的点覆盖所有区间,貌似思路不错,但树上任意两个点并非是一个连续区间,树剖也只能将其分成若干连续区间。06和10兵分两路,我选择了10,卡了很久。4个小时左右01过了,真是辛苦了三位string大佬。然后跟他们说了06和10的题意,也就是把之前想到过的思路重新走了一下,队友zp在4:30左右想到tarjan思想,断成联通块,从叶节点往上贪心,然后我突然想到,两条链的交点必定是这两条链的公共祖先的某一个,于是把想法跟他们说了一下,我很确信这是正确的。zp喊我去说了一下他的思路,然而交流困难,并没有懂他的想法,但大佬觉得很对,于是他干脆自己写。距离比赛结束还有40分钟左右,挺玄的,06题学弟和队友sb在写,然而学弟的代码T了,突然发现09题怎么提交人数这么多,问了一下题意,no sponsor,又发现T<10,status里面好多人都在交09。于是打算最后再试试吧。这题最终还是没做出来,晚上看了一下别人的想法,大概就是我的想法和zp的贪心策略结合一下。
因为任意两条链的交点必定是其中某条的LCA,所以答案必定在这Q个LCA里面选最少个,从叶节点往上相当于将所有lca按深度从大到小排序。但这个dfs序建线段树我是没想到,不过队友还是能想到。今天看了一下题解,原来也不难。每个点的dfs序分为in和out,然后如果某条链的两个节点的dfs序都未被标记,那么ans++,将这条链的lca的in和out在线段树上表示的这段区间标记。遍历到某条链只需看其两个点的dfs序是否被标记过。貌似有点说不清,请参考:Link
理解之后就很简单了。
const int N=1e5+10;
struct node
{
int l,r,f;
} a[N<<2];
struct Edge
{
int to,next;
} e[N];
struct LCA
{
int u,v,father;
} ac[N];
int n,q,head[N],tot,ti,in[N],out[N],dep[N];
int fa[20][N],s[N],t[N];
void init()
{
ti=tot=0;
memset(in,0,sizeof(in));
memset(out,0,sizeof(out));
memset(head,-1,sizeof(head));
memset(fa,-1,sizeof(fa));
}
int cmp(LCA A,LCA B)
{
return dep[A.father]>dep[B.father];
}
void add(int u,int v)
{
e[tot].to=v,e[tot].next=head[u];
head[u]=tot++;
e[tot].to=u,e[tot].next=head[v];
head[v]=tot++;
}
void dfs(int u,int pre,int d)
{
fa[0][u]=pre,dep[u]=d,in[u]=++ti;
for(int i=head[u]; i+1; i=e[i].next)
{
int v=e[i].to;
if(v==pre) continue;
dfs(v,u,d+1);
}
out[u]=++ti;
}
void init_lca()
{
fa[0][1]=-1;
for(int k=0; k<17; k++)
for(int i=1; i<=n; i++)
if(fa[k][i]<0) fa[k+1][i]=-1;
else fa[k+1][i]=fa[k][fa[k][i]];
}
int lca(int u,int v)
{
if(dep[v]>dep[u]) swap(u,v);
for(int k=0; k<17; k++)
if((dep[u]-dep[v])>>k&1) u=fa[k][u];
if(u==v) return u;
for(int i=16; i>=0; i--)
if(fa[i][u]!=fa[i][v])
{
u=fa[i][u];
v=fa[i][v];
}
return fa[0][u];
}
void pushdown(int k)
{
if(a[k].f&&a[k].l!=a[k].r)
{
a[k*2].f=a[k*2+1].f=1;
a[k].f=0;
}
}
void build(int l,int r,int k)
{
a[k].l=l,a[k].r=r,a[k].f=0;
if(l==r) return ;
int mid=(l+r)/2;
build(l,mid,2*k);
build(mid+1,r,2*k+1);
}
void update(int l,int r,int k)
{
if(l<=a[k].l&&a[k].r<=r)
{
a[k].f=1;
return ;
}
pushdown(k);
int mid=(a[k].l+a[k].r)/2;
if(l<=mid) update(l,r,2*k);
if(r>mid) update(l,r,2*k+1);
}
int query(int id,int k)
{
if(a[k].l==a[k].r&&a[k].l==id) return a[k].f;
pushdown(k);
int mid=(a[k].l+a[k].r)/2;
if(id<=mid) return query(id,2*k);
else return query(id,2*k+1);
}
int main()
{
while(~scanf("%d",&n))
{
init();
for(int i=1; i<=n; i++)
{
int u,v;
scanf("%d%d",&u,&v);
add(u+1,v+1);
}
n++;
dfs(1,1,1);
init_lca();
build(1,ti,1);
scanf("%d",&q);
for(int i=0; i<q; i++)
{
scanf("%d%d",&ac[i].u,&ac[i].v);
ac[i].u++,ac[i].v++;
ac[i].father=lca(ac[i].u,ac[i].v);
}
sort(ac,ac+q,cmp);
int ans=0;
for(int i=0; i<q; i++)
if(!query(in[ac[i].u],1)&&!query(out[ac[i].v],1))
{
ans++;
update(in[ac[i].father],out[ac[i].father],1);
}
printf("%d\n",ans);
}
return 0;
}
1012 card card card
第五道过的题,至今还很迷,我们开始看这题的时候也就50几个队过了,但我们AC的时候已经有200+的队AC,可我感觉时间并没有很久啊。题意是他们告诉我的,按着他们的题意把每个a-b压缩成一个数,然后将数组扩展一倍,求最长的大于等于0的序列和,贪心尺取即可。代码也是他们先写出来的,我好菜啊。只能说助攻了。AC时间:2:07
#include<bits/stdc++.h>
using namespace std;
int AA[2000100],BB[2000010];
int main()
{
int n;
while(~scanf("%d",&n))
{
for(int i=0;i<n;i++) scanf("%d",&AA[i]), AA[i+n]=AA[i];
for(int i=0;i<n;i++)
{
scanf("%d",&BB[i]);
BB[i+n]=BB[i];
}
int ans=0;
int SS=0;
int y=0,x=0,l=0,i=0;
while(l<n)
{
y=0;x=0;
i=l;
for(;i<l+n;i++)
{
x=x+AA[i]-BB[i];
y+=AA[i];
if(x<0)
break;
}
if(y>SS) ans=l,SS=y;
l=i+1;
}
printf("%d\n",ans);
}
}
A题卡太久,导致06和10没时间写。貌似新疆赛区的网络赛和沈阳站的都不难,都有原题。。。。。赛后09被吐槽。好吧,在这里也道个歉,最后也交了32发随机。4:40左右有人提议也交随机,此时判题机已经很卡了,我们知道概率很小,看到大家都在疯狂的刷,于是最后10分钟也交了碰碰运气。好吧,我们都不是’天选之人‘。再次对造成影响的队伍表示抱歉。