2019ACM-ICPC徐州网络赛(9.7)
成绩:
rk451,校内排名6。A了7道,我写了CDEK,大佬写了BG,艾继涛写了M。总体表现很差,从3点开始持续宕机到最后,大佬终于2h自学完回文自动机。不但直接与CCPC拜拜,甚至摧毁了自己的自信心。
A. Who is better
比较裸的数学题。首先中国剩余定理解线性同余方程组。之后斐波那契博弈,目前还没来得及看。还没做出来。
代码:待补...
B. so easy
大佬写的...似乎使用权值线段树维护的。复杂度O(n)。
代码:
#include<bits/stdc++.h>
#define inf 1000000000
#define N 1000000
using namespace std;
inline char nc()
{
static char buf[100000],*p1=buf,*p2=buf;
return p1==p2&&(p2=(p1=buf)+fread(buf,1,100000,stdin),p1==p2)?EOF:*p1++;
}
inline int read()
{
int x=0,b=1;
char c=nc();
for(;!(c<='9'&&c>='0');c=nc())if(c=='-')b=-1;
for(;c<='9'&&c>='0';c=nc())x=x*10+c-'0';
return x*b;
}
int n,q;
struct kk{
int op,x;
}Q[N+5];
int cnt,tmp;
int A[2*N+5];
struct tree{
int l,r,mn;
}T[8*N+5];
inline void pushup(int rt)
{
T[rt].mn=min(T[rt*2].mn,T[rt*2+1].mn);
}
inline void build(int rt,int l,int r)
{
T[rt].l=l,T[rt].r=r;
if(T[rt].l==T[rt].r){
T[rt].mn=l;
return;
}
int mid=(T[rt].l+T[rt].r)/2;
build(rt*2,l,mid);
build(rt*2+1,mid+1,r);
pushup(rt);
}
inline void modify(int rt,int pos,int x)
{
if(T[rt].l==pos&&T[rt].r==pos){
T[rt].mn=x;
return;
}
int mid=(T[rt].l+T[rt].r)/2;
if(pos<=mid)modify(rt*2,pos,x);
else modify(rt*2+1,pos,x);
pushup(rt);
}
inline int query(int rt,int l,int r)
{
if(T[rt].l==l&&T[rt].r==r){
return T[rt].mn;
}
int mid=(T[rt].l+T[rt].r)/2;
if(r<=mid)return query(rt*2,l,r);
else if(l>mid)return query(rt*2+1,l,r);
else return min(query(rt*2,l,mid),query(rt*2+1,mid+1,r));
}
int main()
{
//freopen("in.txt","r",stdin);
n=read(),q=read();
for(int i=1;i<=q;i++){
Q[i].op=read();
Q[i].x=read();
}
for(int i=1;i<=q;i++){
cnt++;
A[cnt]=Q[i].x;
cnt++;
A[cnt]=Q[i].x+1;
}
sort(A+1,A+cnt+1);
int tmp=unique(A+1,A+cnt+1)-A-1;
build(1,1,tmp);
for(int i=1;i<=q;i++){
int x=lower_bound(A+1,A+tmp+1,Q[i].x)-A;
if(Q[i].op==1){
modify(1,x,inf);
}else{
int ans=query(1,x,tmp);
printf("%d\n",A[ans]);
}
}
return 0;
}
C. Buy Watermelon
思博题,更思博的是出题人的英语水平。题意是给定w,问w是否能分成两份,使得两份都是偶数。出题人根本不会英语。若w是奇数或w=2,则不能,否则能。
代码:
#include<bits/stdc++.h>
using namespace std;
#define LL long long
#define mid ((l+r)/2)
#define ld long double
#define F first
#define S second
#define ms(x,y) memset(x,y,sizeof(x))
#define F1(i,n) for(int i=1;i<=n;++i)
#define F0(i,n) for(int i=0;i<n;++i)
#define vi vector<int>
#define pii pair<int,int>
#define pb push_back
#define rd(a) scanf("%d",&a)
#define rld(a) scanf("%I64d",&a)
#define rs(a) scanf("%s",a)
const int MAX=4e4,P=1e9+7;
int main(){
int w;
scanf("%d",&w);
if (w%2==1||w==2)
printf("NO\n");
else
printf("YES\n");
return 0;
}
D. Carneginon
比较裸的KMP。分情况匹配即可。注意每次都要先对模式串make_fail()一下。复杂度O(n)
代码:
#include<bits/stdc++.h>
using namespace std;
#define LL long long
#define mid ((l+r)/2)
#define ld long double
#define F first
#define S second
#define ms(x,y) memset(x,y,sizeof(x))
#define F1(i,n) for(int i=1;i<=n;++i)
#define F0(i,n) for(int i=0;i<n;++i)
#define vi vector<int>
#define pii pair<int,int>
#define pb push_back
#define rd(a) scanf("%d",&a)
#define rld(a) scanf("%I64d",&a)
#define rs(a) scanf("%s",a)
const int MAX=1e5+20,P=1e9+7;
char s[MAX],t[MAX];
int fail[MAX];
void make_fail(char *p){
ms(fail,0);
for (int i=1,j=0;p[i];++i){
while (j&&p[i]!=p[j])
j=fail[j-1];
if (p[i]==p[j])
fail[i]=++j;
else
fail[i]=0;
}
}
bool kmp(char *s,char *p){
for (int i=0,j=0;s[i];++i){
while(j&&s[i]!=p[j])
j=fail[j-1];
if (s[i]==p[j]&&!p[++j])
return true;
}
return false;
}
int main(){
scanf("%s",t);
int n,lent=strlen(t);
scanf("%d",&n);
F1(z,n){
scanf("%s",s);
int lens=strlen(s);
if (lens<lent){
make_fail(s);
if (kmp(t,s))
printf("my child!\n");
else
printf("oh, child!\n");
}
else if (lens>lent){
make_fail(t);
if (kmp(s,t))
printf("my teacher!\n");
else
printf("senior!\n");
}
else{
make_fail(t);
if (kmp(s,t))
printf("jntm!\n");
else
printf("friend!\n");
}
}
return 0;
}
E. XKC's basketball team
题意是要对每个人,找到最后一个让他angry的人的位置。我的做法是ST表+二分区间。
首先ST表维护区间最大值。之后枚举每个人i,对i+1到n区间进行二分:每次先看右区间,若右区间max>=m+w[i],则最后一个让他angry的人必然在右区间,选择右边。否则,选择左区间。注意有可能右边没有让他angry的人,所以二分之前先检查一下i+1到n区间的最大值是否>=m+w[i],若不是,直接输出-1。复杂度O(nlogn)。
代码:
#include<bits/stdc++.h>
using namespace std;
#define LL long long
#define mid ((l+r+1)/2)
#define ld long double
#define F first
#define S second
#define ms(x,y) memset(x,y,sizeof(x))
#define F1(i,n) for(int i=1;i<=n;++i)
#define F0(i,n) for(int i=0;i<n;++i)
#define vi vector<int>
#define pii pair<int,int>
#define pb push_back
#define rd(a) scanf("%d",&a)
#define rld(a) scanf("%I64d",&a)
#define rs(a) scanf("%s",a)
const int MAX=5e5+20,P=1e9+7;
int mi2[35],logg[MAX],n,f[MAX][35];
void initst1(){
mi2[0]=1,logg[0]=-1;
F1(i,32)
mi2[i]=mi2[i-1]*2;
F1(i,n)
logg[i]=logg[i/2]+1;
}
void initst2(){
F1(i,logg[n])
F1(j,n+1-mi2[i])
f[j][i]=max(f[j][i-1],f[j+mi2[i-1]][i-1]);
}
int queryst(int l,int r){
int t=r-l+1,lg=logg[t];
return max(f[l][lg],f[r-mi2[lg]+1][lg]);
}
int ans[MAX];
int main(){
int m;
scanf("%d%d",&n,&m);
F1(i,n)
scanf("%d",&f[i][0]);
initst1(),initst2();
F1(i,n){
int l=i+1,r=n;
if (queryst(l,r)<f[i][0]+m)
ans[i]=-1;
else{
while (l!=r){
if (queryst(mid,r)>=f[i][0]+m)
l=mid;
else
r=mid-1;
}
ans[i]=l-i-1;
}
}
F1(i,n-1)
printf("%d ",ans[i]);
printf("%d\n",ans[n]);
return 0;
}
F. Little M's attack plan
听无敌的cyy说是长链剖分,显然是我不会的题目。
代码:待补...
G. Colorful String
标解是回文自动机或回文树,显然也是我不会的题目。
这个题让我们纠结了n久,一开始我的思路是枚举每个位置,后缀数组或者马拉车可以预处理每个位置的最大回文半径,再依次计算。但这样每个位置的复杂度最多是O(n^2)的。后来大佬告诉我一个结论说字符串最多有n个本质不同的回文子串,所以记忆化一下是O(n)的,但又感觉复制字符串会T,就没敢写。随后要自己试一下。
jwp的做法是,预处理每个位置后面第一个出现个字母的位置,这样复杂度是O(26*n)稳过。和M题几乎一样的思路,大佬直接自闭。
代码:
#include<bits/stdc++.h>
#define N 300000
using namespace std;
#define ll long long
using namespace std;
int read()
{
int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
return x*f;
}
ll ans;
int n;
char ch[N+5];
struct pam{
int cnt,last;
int a[2*N+5][26],fa[2*N+5],l[2*N+5],size[2*N+5];
pam(){
cnt=1;fa[0]=fa[1]=1;l[1]=-1;
}
void extend(int c,int n){
int p=last;
while(ch[n-l[p]-1]!=ch[n])p=fa[p];
if(!a[p][c])
{
int now=++cnt,k=fa[p];
l[now]=l[p]+2;
while(ch[n-l[k]-1]!=ch[n])k=fa[k];
fa[now]=a[k][c];a[p][c]=now;
}
last=a[p][c];
size[last]++;
}
void solve(){
for(int i=cnt;i;i--)
{
size[fa[i]]+=size[i];
}
}
}pam;
int L[2*N+5];
int vis[50],tmp;
inline void DFS(int rt)
{
for(int i=0;i<26;i++){
if(vis[i])L[rt]++;
}
for(int i=0;i<26;i++){
if(pam.a[rt][i]){
vis[i]++;
DFS(pam.a[rt][i]);
vis[i]--;
}
}
}
int main()
{
scanf("%s",ch+1);
n=strlen(ch+1);
for(int i=1;i<=n;i++)
pam.extend(ch[i]-'a',i);
pam.solve();
DFS(0);
memset(vis,0,sizeof(vis));tmp=0;DFS(1);
for(int i=1;i<=pam.cnt;i++){
ans+=pam.size[i]*L[i];
}
cout<<ans;
return 0;
}
H. function
一看到数学题就激动,终于能用上自己学过的杜教筛了。最后发现不会做。wzk给了一个分块打表的做法。标解是分块,枚举质数的贡献,我其实也想到了。其实这种情况都是自己没做过所以没有信心也没有处理细节的能力,还是多做题吧。这种分块和质数的题目还是挺配的,多留意。
最后,还是得抽空学一下min_25筛或者洲阁筛了。
代码:待补
I. query
让我宕机两个小时的题。其实就是离线处理。我想到了离线枚举所有的倍数对,一共nlogn个,但就是忘了离线处理查询,还是没做过的事,经验太少了。
思路:建单点加减区间求和的线段树,叶节点表示每个位置。插入一对倍数的操作是,将左边位置的点权值+1。首先将所有倍数的两个对应位置存起来(nlogn对倍数),按右端点从小到大排序,再将询问的两个位置存起来,右端点从小到大排序。之后依次处理查询,处理之前先将所有右端点比查询右端点小的倍数对插入(执行+1操作),之后直接输入[l,r]区间和即可。
可以这样理解:对每个询问,当前只插入了[1,r]之间的所有倍数对,倍数对分成三类,一类是两个端点都在[1,l],第二类是左端点在[1,l],右端点在[l,r],第三类是两个端点都在[l,r]。我们这里只算第三类,第三类的每一对左端点都+1,都在[l,r]之间。但一二类的左端点都在[1,l],所以不会被计入。
该问题还可以抽象为:给定n个小区间,给q次询问,每次给一个大区间,问大区间内有几个小区间。就用这种全离线做法。复杂度为O(nlognlog(nlogn))
代码:
#include<bits/stdc++.h>
using namespace std;
#define LL long long
#define mid ((l+r)/2)
#define ld long double
#define F first
#define S second
#define ms(x,y) memset(x,y,sizeof(x))
#define F1(i,n) for(int i=1;i<=n;++i)
#define F0(i,n) for(int i=0;i<n;++i)
#define vi vector<int>
#define pii pair<int,int>
#define pb push_back
#define rd(a) scanf("%d",&a)
#define rld(a) scanf("%I64d",&a)
#define rs(a) scanf("%s",a)
const int MAX=2e5,P=1e9+7;
int v[MAX*4];
LL query(LL id,LL l,LL r,LL ql,LL qr){
if (ql<=l&&r<=qr)
return v[id];
LL ans=0;
if (ql<=mid)
ans+=query(id*2,l,mid,ql,qr);
if (mid<qr)
ans+=query(id*2+1,mid+1,r,ql,qr);
return ans;
}
void operate(LL id,LL l,LL r,LL pos,LL x){
if (l==r){
v[id]+=x;
return;
}
if (pos<=mid)
operate(id*2,l,mid,pos,x);
if (mid<pos)
operate(id*2+1,mid+1,r,pos,x);
v[id]=v[id*2]+v[id*2+1];
}
int a[MAX],a_rev[MAX],ans[MAX];
struct pi{
int first,second,third;
pi(int a=0,int b=0,int c=0){
third=c;
if (a>b)
first=b,second=a;
else
first=a,second=b;
}
};
struct cmp{
bool operator()(pi x,pi y){
return x.S>y.S;
}
};
priority_queue<pi,vector<pi>,cmp> pq,que;
int main(){
int n,m;
scanf("%d%d",&n,&m);
F1(i,n)
scanf("%d",&a[i]),a_rev[a[i]]=i;
F1(i,n)
for (int j=i*2;j<=n;j+=i)
pq.push(pi(a_rev[i],a_rev[j],i));
int c1,c2;
F1(i,m)
scanf("%d%d",&c1,&c2),que.push(pi(c1,c2,i));
while (!que.empty()){
int c1=que.top().F,c2=que.top().S,c3=que.top().third;
que.pop();
while (!pq.empty()&&pq.top().S<=c2)
operate(1,1,n,pq.top().F,1),pq.pop();
if (c1!=c2)
ans[c3]=query(1,1,n,c1,c2);
else
ans[c3]=0;
}
F1(i,m)
printf("%d\n",ans[i]);
return 0;
}
J. Random Access Iterator
题意:一个人要dfs求一棵树的深度,但枚举每个儿子的时候并不是依次选儿子,而是随机选,问输出正确答案的概率。
树上dp,注意转移即可。
代码:待补...
K.Center
比较简单的计算几何。直接暴力枚举所有点对,算出中心,用哈希或者map存结果,map[pair]=j,表示如果选择pair为中心,则会有j个点不需要额外的点来与他们配对。
所以读入点的时候,直接对每个点坐标+1。每算出一个中心,中心就+2。最后输出n-max即可。
注意特判1个点的情况!!!复杂度为O(n^2)。
代码:
#include<bits/stdc++.h>
using namespace std;
#define LL long long
#define mid ((l+r)/2)
#define ld long double
#define F first
#define S second
#define ms(x,y) memset(x,y,sizeof(x))
#define F1(i,n) for(int i=1;i<=n;++i)
#define F0(i,n) for(int i=0;i<n;++i)
#define vi vector<int>
#define pii pair<double,double>
#define pb push_back
#define rd(a) scanf("%d",&a)
#define rld(a) scanf("%I64d",&a)
#define rs(a) scanf("%s",a)
const int MAX=1e3+20,P=1e9+7;
int x[MAX],y[MAX];
map<pii,int> mp; //ok
int main(){
int n,c,mx=0;
scanf("%d",&n);
F1(i,n)
scanf("%d%d",&x[i],&y[i]),++mp[pii(x[i],y[i])];
F1(i,n)
for (int j=i+1;j<=n;++j){
c=(mp[pii((x[i]+x[j])/2.0,(y[i]+y[j])/2.0)]+=2);
if (c>mx)
mx=c;
}
if (n==1)
printf("0\n");
else
printf("%d\n",n-mx);
return 0;
}
L. Dice
没读题,听无敌的wzk也没听懂。
代码:不补了。
M. Longest subsequence
问最长子序列,一开始以为是最长子串,最后艾继涛写的,我也没怎么懂。
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<string>
#include<queue>
#include<vector>
#include<stack>
#include<map>
using namespace std;
int n,m;
char s[1000006];
char t[1000006];
int KP[26][1000006];
//int OP[26][1000006];
int ans=0;
int STA=0;
int tail=1;
int main()
{
int i,j,k,l;
scanf("%d%d",&n,&m);
scanf("%s",s+1);
scanf("%s",t+1);
for(i=n;i>=1;i--){
for(j=0;j<26;j++){
if((s[i]-'a')>j)KP[j][i]=i;
else KP[j][i]=KP[j][i+1];
}
}
if(KP[t[1]-'a'][1]!=0)ans=max(ans,n-KP[t[1]-'a'][1]+1);
for(i=1;i<=n;i++){
if(t[tail]==s[i]){
tail++;
if(tail<=m)if(KP[t[tail]-'a'][i+1]!=0)ans=max(ans,n-KP[t[tail]-'a'][i+1]+tail);
if(tail==(m+1)&&i!=n){
ans=max(ans,n-i-1+tail);
break;
}
}
}
if(ans==0)ans=-1;
printf("%d\n",ans);
return 0;
}
总结
今天打的一坨屎。qz爷队都过了10个,直追sl队,我们还是停留在7个题,关键的G和L都没做出来。其实想的很接近了,就差最后一步,很可惜。如果之前见过类似的,比如说I如果见过离线查询,我也就会做了。
希望这是最后一次倒二。
浙公网安备 33010602011771号