T1:Censoring
和以前kmp一样的一道题,只是改成了多个串需要AC自动机
用一个栈维护当前字符串,匹配上了就暴力弹栈,并将指针回溯,复杂度O(n+m)
这题考试的时候不知道怎么把栈给否掉了,用了个玄学方法记录,只干出来13分
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
#define null NULL
using namespace std;
struct node{
node *fi,*ch[26];
int c;
node (){
fi=null;c=0;
memset(ch,null,sizeof(ch));
}
}*root=new node();
char a[2002][100002];int le[2002];
short s[200];
void insert(int x){
node *now=root;int len=le[x];
for(int i=1;i<=len;i++){
int in=s[a[x][i]];
if(now->ch[in]==null) now->ch[in]=new node();
now=now->ch[in];
}
now->c=x;
}
void getfail(){
queue<node*>q;
for(int i=0;i<26;i++)
if(root->ch[i]!=null){
root->ch[i]->fi=root;
q.push(root->ch[i]);
}
else root->ch[i]=root;
while(!q.empty()){
node *now=q.front();q.pop();
for(int i=0;i<26;i++)
if(now->ch[i]!=null){
now->ch[i]->fi=now->fi->ch[i];
q.push(now->ch[i]);
}
else now->ch[i]=now->fi->ch[i];
}
}
node *last[100002];
int top,sol[100002];char sta[100002];
void query(){
node *now=root;int len=le[0],del=0;
for(int i=1;i<=len;i++){
if(now==null) now=root;
sta[++top]=a[0][i];last[top]=now;
int out=s[a[0][i]];
now=now->ch[out];
if(now->c){
top-=le[now->c];
now=last[top+1];
}
}
}
int main(){
for(int i='a';i<='z';i++) s[i]=i-'a';
for(int i='A';i<='Z';i++) s[i]=i-'A';
scanf("%s",a[0]+1);le[0]=strlen(a[0]+1);
int n;
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%s",a[i]+1);
le[i]=strlen(a[i]+1);
insert(i);
}
getfail();
query();
for(int i=1;i<=top;i++)
putchar(sta[i]);
return 0;
}
T2:记忆的轮廓
概率期望,考试时候直接弃了(主要是无良老师数据范围没给)
50%:数据n==p,则不需要考虑存档,首先设d[i]表示i的儿子数。设g[i]表示对于一个错误节点i,期望走多少步会读档。那么
g[i]=1+1/d[i]*∑{g[j]}其中j是i的儿子。
接下来我们倒着递推:f[i]=1+1/d[i]*f[i+1]+1/d[i]*∑{g[j]+f[i]}[j是i的错误儿子]
移项得:f[i]=d[i]+f[i+1]+Σg[j][j是i的错误儿子] 复杂度O(m)
70%:我们设dp,f[i,j]表示当前存档点为i,还剩j次存档机会。
首先我们需要预处理一个a[i,j],表示存档点为i,从i开始走到正确节点j的期望步数(中间不能存档)。
显然有边界条件a[i,i]=0。对于i<j,可以列出递推式:
a[i,j]=a[i,j-1]+1+1/d[j-1]*∑{g[k]+a[i,j]}[k是j-1的错误儿子]
移项得a[i,j]=a[i,j-1]*d[j-1]+d[j-1]+Σg[k][k是j-1的错误儿子]
则f[i][k]=min(f[j][k-1]+a[i][j]),答案f[1][p-1]
复杂度O(pn^2)
100%:优化方向1:我们观察a数组,发现有a[i,j]<a[i,j+1],a[i-1][j]>a[i][j]
那么我们可以用单调队列优化,用两维的单调队列(当前和上一次)每次把所有f[i][k]插入
每次查询的时候若队头que[st].dp+a[i][que[st].at]>=que[st+1].dp+a[i][que[st+1].at]||que[st].at<=i就出队
然后理论上我们还需要二分来找到两个函数交点
但是我没找它就过了......
复杂度O(pn)
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
#include<vector>
#define re register
using namespace std;
struct node{
int at;
double dp;
}que[2000][2];
double g[2005],f[2005][705],d[2005],a[2005][2005];
int to[2000][5],st[2],ed[2],now;
void dfs(int u){
int d=to[u][0];
g[u]=1;
for(int i=1;i<=d;i++){
dfs(to[u][i]);
g[u]+=g[to[u][i]]/d;
}
}
int main(){
int T,n,m,p,x,y;
scanf("%d",&T);
while(T--){
memset(f,127,sizeof(f));memset(a,0,sizeof(a));memset(to,0,sizeof(to));
scanf("%d%d%d",&n,&m,&p);p--;
for(int i=1;i<=m-n;i++){
scanf("%d%d",&x,&y);
to[x][++to[x][0]]=y;
}
for(int i=1;i<=n-1;i++){
d[i]=to[i][0];g[i]=0;
for(int j=1;j<=d[i];j++){
dfs(to[i][j]);
g[i]+=g[to[i][j]];
}
d[i]+=1;
}
for(int i=1;i<=n-1;i++)
for(int j=i+1;j<=n;j++)
a[i][j]=a[i][j-1]*d[j-1]+g[j-1]+d[j-1];
st[now]=1;ed[now]=0;
que[++ed[now]][now].dp=0;
que[ed[now]][now].at=n;
for(int k=1;k<=p;k++){
now^=1;
st[now]=1;ed[now]=0;
for(int i=1;i<=n-1;i++){
while(st[now^1]<ed[now^1]&&(que[st[now^1]][now^1].at<=i||que[st[now^1]][now^1].dp+a[i][que[st[now^1]][now^1].at]>=que[st[now^1]+1][now^1].dp+a[i][que[st[now^1]+1][now^1].at])) st[now^1]++;
f[i][k]=que[st[now^1]][now^1].dp+a[i][que[st[now^1]][now^1].at];
}
for(int i=1;i<=n-1;i++) que[++ed[now]][now].dp=f[i][k],que[ed[now]][now].at=i;
que[++ed[now]][now].dp=0;que[ed[now]][now].at=n;
}
printf("%.4lf
",f[1][p]);
}
return 0;
}
优化方向2:观察a数组,可以看到它是恐怖的增长的
我们来估计答案的上界,考虑一种可行方案,每n/p个正确节点就设立一次存档位置,那么答案最大是多少呢?考虑最坏情况,观
察a的转移,应该每变换一次存档点,
设s[i]=Σg[j]大约需要3^(n/p)s[i]+3(n/p-1)*s[i+1]+3^(n/p-2)*s[i+2]+……因为最多m个节点,s的上限是1500(实际上也远远达不
到)
那么其实,针对答案不会特别大,a的增长又很恐怖,我们还可以思考对70%的算法优化。那就是设定一个常数step,每次转移最
多从距当前step步远的位置转移过来。step取40多基本不会有问题了,因为a的下界已经是2^40了,而答案的上界远远没有
达到,经过精确计算还可以再把step调小一点。
复杂度O(np log ans)
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
#include<vector>
#define re register
using namespace std;
struct node{
int at;
double dp;
};
deque<node>q;
double g[2005],f[2005][705],d[2005],a[2005][2005];
int to[2000][5];
void dfs(int u){
int d=to[u][0];
g[u]=1;
for(int i=1;i<=d;i++){
dfs(to[u][i]);
g[u]+=g[to[u][i]]/d;
}
}
int main(){
int T,n,m,p,x,y;
scanf("%d",&T);
while(T--){
memset(f,127,sizeof(f));memset(a,0,sizeof(a));memset(to,0,sizeof(to));
scanf("%d%d%d",&n,&m,&p);p--;
for(int i=1;i<=m-n;i++){
scanf("%d%d",&x,&y);
to[x][++to[x][0]]=y;
}
for(int i=1;i<=n-1;i++){
d[i]=to[i][0];g[i]=0;
for(int j=1;j<=d[i];j++){
dfs(to[i][j]);
g[i]+=g[to[i][j]];
}
d[i]+=1;
}
for(int i=1;i<=n-1;i++)
for(int j=i+1;j<=n;j++)
a[i][j]=a[i][j-1]*d[j-1]+g[j-1]+d[j-1];
f[n][0]=0;
for(int k=1;k<=p;k++)
for(int i=n-1;i>=1;i--)
for(int j=i+1;j<=n&&j-i<=40;j++)
{
f[i][k]=min(f[i][k],f[j][k-1]+a[i][j]);
if(f[j+1][k-1]>f[j][k-1]) break;
}
printf("%.4lf
",f[1][p]);
}
return 0;
}
T3:动态开点+权值线段树合并+树上差分
考试时候想到了没敢打,主要没分析好空间复杂度
对每个点开权值线段树,离不离散都行
复杂度时间,空间都是O(nlogn)
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
#define re register
#define L ls[k]
#define R rs[k]
#define lc ls[k],l,mid
#define rc rs[k],mid+1,r
using namespace std;
inline int read(){
re int a=0;re char ch=getchar();
while(ch<'0'||ch>'9') ch=getchar();
while(ch>='0'&&ch<='9') a=(a<<3)+(a<<1)+ch-'0',ch=getchar();
return a;
}
int rt[100005],ans[100005],ls[5000000],rs[5000000],da[5000000],d[100005],f[100005][20],fr[100005],to[200005],la[200005],cnt,num,is[100005],tot;
inline void add(int x,int y){
to[++num]=y;
la[num]=fr[x];
fr[x]=num;
}
void dfs(int u,int fa){
for(int i=1;(1<<i)<=d[u];i++) f[u][i]=f[f[u][i-1]][i-1];
for(int i=fr[u];i;i=la[i])
if(to[i]!=fa){
d[to[i]]=d[u]+1;f[to[i]][0]=u;
dfs(to[i],u);
}
}
int getlca(int a,int b){
if(d[a]<d[b]) swap(a,b);
for(int i=18;i>=0;i--)
if(d[a]-(1<<i)>=d[b]) a=f[a][i];
if(a==b) return a;
for(int i=18;i>=0;i--)
if(f[a][i]!=f[b][i]) a=f[a][i],b=f[b][i];
return f[a][0];
}
void insert(int &k,int l,int r,int x,int y){
if(!k) k=++cnt;
if(l==r){
da[k]+=y;
return;
}
re int mid=(l+r)>>1;
if(x<=mid) insert(lc,x,y);
else insert(rc,x,y);
da[k]=max(da[L],da[R]);
}
int merge(int x,int y,int l,int r){
if(!x||!y) return x+y;
if(l==r){
da[x]+=da[y];
return x;
}
re int mid=(l+r)>>1;
ls[x]=merge(ls[x],ls[y],l,mid);
rs[x]=merge(rs[x],rs[y],mid+1,r);
da[x]=max(da[ls[x]],da[rs[x]]);
return x;
}
int query(int k,int l,int r){
if(!da[k]||!k) return 0;
if(l==r) return l;
re int mid=(l+r)>>1;
if(da[L]>=da[R]) return query(lc);
else return query(rc);
}
void dfss(int u,int fa){
for(int i=fr[u];i;i=la[i])
if(to[i]!=fa){
dfss(to[i],u);
rt[u]=merge(rt[u],rt[to[i]],1,tot);
}
ans[u]=is[query(rt[u],1,tot)];
}
struct node{
int x,y,z;
}Q[100005];
inline int as(node x,node y){
return x.z<y.z;
}
int main(){
int x,y,z,n=read(),m=read();
for(int i=1;i<n;i++){
x=read();y=read();
add(x,y);add(y,x);
}
d[1]=1;
dfs(1,0);
for(int i=1;i<=m;i++)
Q[i].x=read(),Q[i].y=read(),Q[i].z=read();
sort(Q+1,Q+m+1,as);
for(int i=1;i<=m;i++){
if(Q[i].z!=Q[i+1].z) is[++tot]=Q[i].z,Q[i].z=tot;
else Q[i].z=tot+1;
}
for(int i=1;i<=m;i++){
int lca=getlca(Q[i].x,Q[i].y);
insert(rt[Q[i].x],1,tot,Q[i].z,1);
insert(rt[Q[i].y],1,tot,Q[i].z,1);
insert(rt[lca],1,tot,Q[i].z,-1);
insert(rt[f[lca][0]],1,tot,Q[i].z,-1);
}
dfss(1,0);
for(int i=1;i<=n;i++) printf("%d
",ans[i]);
return 0;
}
这次考试总体状态还可以,就是对于一些东西想的不深入,没有透彻理解
主要就是第一题没有拿到应该的分,当时感觉是一道水题没有当回事,码的时候没有注意细节,自己编了几个样例过了就以为稳了
以后对于水题一定得仔细,否则别的题得分再高也没用
以上。