PKUSC 模拟赛 day2 下午总结

终于考完了,下午身体状况很不好,看来要锻炼身体了,不然以后ACM没准比赛到一半我就挂掉了

下午差点AK,有一道很简单的题我看错题面了所以没有A掉

第一题显然是非常丝薄的题目

我们很容易通过DP来O(n^2)的求出深度至多为k的方案

然后我们很容易通过DP来O(n^2)的求出深度至多为k-1的方案

转移的时候分当前放左括号或者右括号讨论就可以了

两个作差就是答案了,单次询问时间复杂度O(n^2)

如果给dp加一维O(n^3)预处理,O(1)回答

当然要写高精度

成功抢到下午的first blood,请叫我手速狗,汪

#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<iostream>
#include<algorithm>
using namespace std; const int mod=10000;
int n,k,kase;
struct big_num{
int a[22],len;
void add(const big_num &A){
len=max(A.len,len)+1;
for(int i=1;i<=len;++i)a[i]=a[i]+A.a[i];
for(int i=1;i<=len;++i){
a[i+1]+=a[i]/mod;
a[i]%=mod;
}
while(len>0&&a[len]==0)len--;
}
void minus(const big_num &A){
for(int i=1;i<=len;++i){
if(a[i]<A.a[i])a[i+1]--,a[i]=a[i]+mod-A.a[i];
else a[i]=a[i]-A.a[i];
}
while(len>0&&a[len]==0)len--;
}
void print(){
printf("Case %d: ",kase);
printf("%d",a[len]);
for(int i=len-1;i>=1;--i)printf("%04d",a[i]);
printf("\n\n");
}
void clear(){len=1;memset(a,0,sizeof(a));}
}f[102][52],ans; int main(){
while(scanf("%d%d",&n,&k)==2){
if(!n&&!k)break;
f[0][0].len=1;f[0][0].a[1]=1;
n<<=1;kase++;
for(int i=1;i<=n;++i){
for(int j=0;j<=k;++j){
f[i][j].clear();
if(j>0)f[i][j].add(f[i-1][j-1]);
if(j<k)f[i][j].add(f[i-1][j+1]);
}
}
ans=f[n][0];
for(int i=1;i<=n;++i){
for(int j=0;j<=k-1;++j){
f[i][j].clear();
if(j>0)f[i][j].add(f[i-1][j-1]);
if(j<k-1)f[i][j].add(f[i-1][j+1]);
}
}
ans.minus(f[n][0]);
ans.print();
}return 0;
}

第二题保证每个点恰好属于一个环,求最长环

我能想到的有两种做法:

1、这题目显然是点双的裸题QAQ

2、用并查集建树,之后枚举非树边计算环的大小更新答案

由于上午点双的阴影还在,所以果断写了第二种做法

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<cstdlib>
using namespace std; const int maxn=5010;
int T,n,m;
int f[maxn];
int h[maxn],cnt=0;
bool check[maxn];
struct edge{
int to,next;
}G[3000010];
struct Edge{
int u,v;
}c[3000010];
int fa[maxn],dep[maxn];
int anc[maxn][20]; void read(int &num){
num=0;char ch=getchar();
while(ch<'!')ch=getchar();
while(ch>='0'&&ch<='9')num=num*10+ch-'0',ch=getchar();
}
void add(int x,int y){
++cnt;G[cnt].to=y;G[cnt].next=h[x];h[x]=cnt;
}
int ufs(int x){
return f[x]==x?x:f[x]=ufs(f[x]);
}
void DFS(int u,int f){
fa[u]=f;
for(int i=h[u];i;i=G[i].next){
int v=G[i].to;
if(v==f)continue;
dep[v]=dep[u]+1;
DFS(v,u);
}return;
}
void pre_LCA(){
for(int i=1;i<=n;++i){
anc[i][0]=fa[i];
for(int j=1;(1<<j)<=n;++j)anc[i][j]=-1;
}
for(int j=1;(1<<j)<=n;++j){
for(int i=1;i<=n;++i){
if(anc[i][j-1]!=-1){
int a=anc[i][j-1];
anc[i][j]=anc[a][j-1];
}
}
}return;
}
int LCA(int p,int q){
if(dep[p]<dep[q])swap(p,q);
int log;
for(log=0;(1<<log)<=dep[p];++log);--log;
for(int i=log;i>=0;--i){
if(dep[p]-(1<<i)>=dep[q])p=anc[p][i];
}
if(p==q)return p;
for(int i=log;i>=0;--i){
if(anc[p][i]!=-1&&anc[p][i]!=anc[q][i]){
p=anc[p][i];q=anc[q][i];
}
}return fa[p];
} int main(){
read(T);
while(T--){
read(n);read(m);
memset(h,0,sizeof(h));cnt=0;
for(int i=1;i<=n;++i)f[i]=i,dep[i]=0;
for(int i=1;i<=m;++i)check[i]=false;
for(int i=1;i<=m;++i){
read(c[i].u);read(c[i].v);
int d1=ufs(c[i].u),d2=ufs(c[i].v);
if(d1!=d2){
f[d1]=d2;
add(c[i].u,c[i].v);
add(c[i].v,c[i].u);
check[i]=true;
}
}
for(int i=1;i<=n;++i)if(ufs(i)==i)DFS(i,-1);
pre_LCA();
int ans=0;
for(int i=1;i<=m;++i){
if(!check[i]){
int lca=LCA(c[i].u,c[i].v);
ans=max(ans,dep[c[i].u]+dep[c[i].v]-2*dep[lca]+1);
}
}printf("%d\n",ans);
}return 0;
}

第三题是原题QAQ

我背后缀数组模板的时候背的题目

曾经跟lyc说过这道题目可以用SAM做,然后是论文题

然而SAM的极限时间复杂度是O(n*sqrt(n)),感觉多笔测资有些虚

考试后发现他们都写得SAM QAQ 貌似可以卡住的样子 QAQ

后缀数组码码码,明显后缀数组很不熟练,写了30min才写完

比其他模板写的慢多了QAQ 然后除了被系统卡了rank的关键字之外1A

真是舒爽

#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int maxn=1000010;
int n,L,R,kase;
int s[maxn],now;
int cnt=-1;
int pos[maxn];
int wa[maxn],wb[maxn],w[maxn],wv[maxn];
int sa[maxn],height[maxn],rk[maxn];
int vis[1010],tim;
void get(int id){
char ch=getchar();
while(ch<'!')ch=getchar();
while(ch>='a'&&ch<='z'){s[++cnt]=(int)(ch)+200;pos[cnt]=id;ch=getchar();}
s[++cnt]=++now;
}
bool cmp(int *r,int a,int b,int l){return r[a]==r[b]&&r[a+l]==r[b+l];}
void get_da(int n,int m){
int *x=wa,*y=wb,*t;
for(int i=0;i<m;++i)w[i]=0;
for(int i=0;i<n;++i)w[x[i]=s[i]]++;
for(int i=1;i<m;++i)w[i]+=w[i-1];
for(int i=n-1;i>=0;--i)sa[--w[x[i]]]=i;
for(int p=1,j=1;p<n;j<<=1,m=p){
p=-1;
for(int i=n-j;i<n;++i)y[++p]=i;
for(int i=0;i<n;++i)if(sa[i]>=j)y[++p]=sa[i]-j;
for(int i=0;i<n;++i)wv[i]=x[y[i]];
for(int i=0;i<m;++i)w[i]=0;
for(int i=0;i<n;++i)w[wv[i]]++;
for(int i=1;i<m;++i)w[i]+=w[i-1];
for(int i=n-1;i>=0;--i)sa[--w[wv[i]]]=y[i];
t=x;x=y;y=t;x[sa[0]]=0;p=1;
for(int i=1;i<n;++i)x[sa[i]]=cmp(t,sa[i],sa[i-1],j)?p-1:p++;
}
for(int i=0;i<n;++i)rk[sa[i]]=i;
int k=0;
for(int i=0;i<n;++i){
if(rk[i]==0){height[rk[i]]=0;continue;}
if(k)--k;
int j=sa[rk[i]-1];
while(s[j+k]==s[i+k])k++;
height[rk[i]]=k;
}return;
}
bool check(int blo){
int tot=0;
for(int i=n;i<=cnt;++i){
if(height[i]<blo){
tim++;tot=1;
vis[pos[sa[i]]]=tim;
}
else{
if(vis[pos[sa[i]]]!=tim)vis[pos[sa[i]]]=tim,tot++;
if(tot>n/2)return true;
}
}return false;
}
void print(){
if(L==0){printf("?\n");return;}
int tot=0;
for(int i=n;i<=cnt;++i){
if(height[i]<L){
tim++;
if(tot>n/2){
for(int j=sa[i-1];j<=sa[i-1]+L-1;++j)putchar(s[j]-200);
printf("\n");
}tot=1;vis[pos[sa[i]]]=tim;
}else{
if(vis[pos[sa[i]]]!=tim)vis[pos[sa[i]]]=tim,tot++;
}
}
if(tot>n/2){
for(int j=sa[cnt];j<=sa[cnt]+L-1;++j)putchar(s[j]-200);
printf("\n");
}return;
}
int main(){
while(scanf("%d",&n)==1){
if(n==0)break;
if(kase)printf("\n");
kase++;cnt=-1;now=0;
for(int i=1;i<=n;++i)get(i);
get_da(cnt+1,1000);
memset(vis,0,sizeof(vis));tim=1;
L=0,R=cnt;
while(L<R){
int mid=L+(R-L+1)/2;
if(check(mid))L=mid;
else R=mid-1;
}
print();
}return 0;
}

第四题好难读的题面,然后读懂了就很容易发现是个斗地主的弱化版

随便写写就好啦

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<cstdlib>
using namespace std; int T,kase;
char s[22][22];
char t[22][22];
int vis[4][12];
int tmp[1010]; bool DFS(int num){
if(num==12){
for(int i=1;i<=3;++i){
for(int j=1;j<=9;++j){
if(vis[i][j]==2)return true;
}
}return false;
}
for(int i=1;i<=3;++i){
for(int j=1;j<=9;++j){
if(vis[i][j]){
if(vis[i][j]>=3){
vis[i][j]-=3;
if(DFS(num+3))return true;
vis[i][j]+=3;
}
if(vis[i][j+1]&&vis[i][j+2]){
vis[i][j]--;
vis[i][j+1]--;
vis[i][j+2]--;
if(DFS(num+3))return true;
vis[i][j]++;
vis[i][j+1]++;
vis[i][j+2]++;
}
}
}
}return false;
} bool check(){
memset(vis,0,sizeof(vis));
for(int i=1;i<=14;++i)vis[tmp[s[i][2]]][s[i][1]-'0']++;
for(int i=1;i<=3;++i){
for(int j=1;j<=9;++j){
if(vis[i][j]>4)return false;
}
}
if(DFS(0))return true;
return false;
} int main(){
scanf("%d",&T);
tmp['s']=1;tmp['b']=2;tmp['c']=3;
while(T--){
kase++;int cnt=0;
for(int i=1;i<=13;++i)scanf("%s",s[i]+1);
printf("Case %d:",kase);
for(int i=1;i<=3;++i){
if(i==1)s[14][2]='s';
else if(i==2)s[14][2]='b';
else s[14][2]='c';
for(int j=1;j<=9;++j){
s[14][1]=j+'0';
if(check()){
printf(" ");cnt++;
putchar(s[14][1]);
putchar(s[14][2]);
}
}
}
if(!cnt)printf(" None");
printf("\n");
}return 0;
}

第五题考试的时候一直看错题,然后就没有A掉

后来听lyc说了题意之后发现是个背包的丝薄题

看对题之后随便写写就A啦

正确性的证明是因为答案是个二次函数QAQ

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<cstdlib>
using namespace std; typedef long long LL;
const int maxn=110;
const LL oo=1LL<<60;
int n,L;
LL sa,sc;
int c[maxn],a[maxn];
LL f[maxn][maxn][502]; int main(){
scanf("%d%d",&n,&L);
for(int i=1;i<=n;++i)scanf("%d",&a[i]),sa+=a[i];
for(int i=1;i<=n;++i)scanf("%d",&c[i]),sc+=c[i];
for(int i=0;i<=n;++i)for(int j=0;j<=n;++j)for(int k=0;k<=sa;++k)f[i][j][k]=oo;
f[0][0][0]=0;
for(int i=1;i<=n;++i){
for(int j=0;j<=n;++j){
for(int k=0;k<=sa;++k){
f[i][j][k]=f[i-1][j][k];
if(k>=a[i]&&j>=1)f[i][j][k]=min(f[i][j][k],f[i-1][j-1][k-a[i]]+c[i]);
}
}
}
double ans=1e13;
for(int i=1;i<=n;++i){
if(i==L||n-i==L){
for(int k=1;k<=sa-1;++k){
if(f[n][i][k]!=oo){
double now=(double)(f[n][i][k])/(double(k))*(double)(sc-f[n][i][k])/(double)(sa-k);
ans=min(ans,now);
}
}
}
}printf("%.3lf\n",ans);
return 0;
}

第六题正解是维护凸包,每次二分斜率求最优值做DP就可以了

然后考试的时候没有太多时间,很容易发现答案是fi+ci*abs(xi-xj)的形式

两边项次数都是一次

下面不加证明的给出以下结论:

1、当两边项都是二次的时候,使用KD_Tree并定期重构保证不退化时间复杂度较优

2、当两边项一个一次一个二次的时候,使用平衡树以二次项为键值维护并做KD_Tree的查询较优

3、当两边项均为一次时,使用线段树同时维护两个信息并做KD_Tree的查询较优

然后本题显然两个一次直接线段树+KD_Tree就可以啦

其实这个题目是式子是省选模拟某套题目的第一题的式子?

#include<cstdio>
#include<cstring>
#include<iostream>
#include<cstdlib>
#include<algorithm>
#include<cmath>
using namespace std; typedef long long LL;
const int maxn=100010;
const int oo=0x7fffffff;
int n;
int x[maxn],c[maxn],t[maxn];
int l[maxn<<2],r[maxn<<2];
LL mxd[maxn<<2],mxc[maxn<<2];
LL dp[maxn],now,k; void UPD(int o,int L,int R,int p,LL d,LL c,int x){
if(L==R){mxd[o]=d;mxc[o]=c;l[o]=x;r[o]=x;return;}
int mid=(L+R)>>1;
if(p<=mid)UPD(o<<1,L,mid,p,d,c,x);
else UPD(o<<1|1,mid+1,R,p,d,c,x);
mxd[o]=max(mxd[o<<1],mxd[o<<1|1]);
mxc[o]=max(mxc[o<<1],mxc[o<<1|1]);
l[o]=min(l[o<<1],l[o<<1|1]);
r[o]=max(r[o<<1],r[o<<1|1]);
}
void ask(int o,int L,int R,int x,int y){
if(L>=x&&R<=y){
if(L==R)now=max(now,mxd[o]+mxc[o]*abs(l[o]-k));
else{
LL d1=mxd[o<<1]+mxc[o<<1]*max(abs(l[o<<1]-k),abs(r[o<<1]-k));
LL d2=mxd[o<<1|1]+mxc[o<<1|1]*max(abs(r[o<<1|1]-k),abs(l[o<<1|1]-k));
int mid=(L+R)>>1;
if(d1>d2){
if(d1>now)ask(o<<1,L,mid,x,y);
if(d2>now)ask(o<<1|1,mid+1,R,x,y);
}else{
if(d2>now)ask(o<<1|1,mid+1,R,x,y);
if(d1>now)ask(o<<1,L,mid,x,y);
}
}return;
}
int mid=(L+R)>>1;
if(y<=mid)ask(o<<1,L,mid,x,y);
else if(x>mid)ask(o<<1|1,mid+1,R,x,y);
else ask(o<<1,L,mid,x,y),ask(o<<1|1,mid+1,R,x,y);
} int main(){
scanf("%d",&n);
for(int i=1;i<=n;++i)scanf("%d%d%d",&x[i],&c[i],&t[i]);
for(int i=1;i<=(n<<2);++i)l[i]=oo,r[i]=-oo;
UPD(1,1,n,1,0,c[1],x[1]);
for(int i=2;i<=n;++i){
now=0;k=x[i];
ask(1,1,n,1,i-1);
dp[i]=now+t[i];
UPD(1,1,n,i,dp[i],c[i],x[i]);
}printf("%lld\n",dp[n]);
return 0;
}

今天下午发挥的较为不错

直接说缺点吧:

1、第三题的后缀数组模板不熟练,拖了一些时间

2、身体素质不太好,在四点半以后就开始狂吐不止,没有什么精力去仔细看第五题,导致第五题并没有A掉

上一篇:LightOJ 1234 Harmonic Number 调和级数部分和


下一篇:Spring.Net+Nhibernate+Asp.Net Mvc 框架