Day1T1

思路

炒鸡良心的模拟题

代码:

#include<cstdio>
#include<iostream>
using namespace std;
const int N=42;
int a[N][N],x,y,ans;
int main() {
    int n;
    cin>>n;
    x=1;
    y=n/2+1;
    a[x][y]=++ans;
    while(ans<n*n) {
        if(x==1&&y!=n) {
            x=n;
            y++;
            a[x][y]=++ans;
        }
        else if(y==n&&x!=1) {
            y=1;
            x--;
            a[x][y]=++ans;
        }
        else if(x==1&&y==n) {
            x++;
            a[x][y]=++ans;
        }
        else if(a[x-1][y+1]==0) {
            x--;
            y++;
            a[x][y]=++ans;
        }
        else {
            x++;
            a[x][y]=++ans;
        }
    }
    for(int i=1;i<=n;++i) {
        for(int j=1;j<=n;++j) {
            printf("%d ",a[i][j]);
        }
        printf("\n");
    }


    return 0;
}

Day1T2

思路

不难发现,这个题就是找信息传递所形成的有向图中的最小环,dfs即可。

代码:

#include<cstdio>
#include<iostream>
using namespace std;
const int N=200000+1000,INF=0x7fffffff;
int dis[N],son[N];
int n,flag,ans=INF,js,beg;
void work(int x) {
    dis[x]=++js;
    if(dis[son[x]]) { 
        if(dis[son[x]]>beg)
            ans=min(ans,dis[x]-dis[son[x]]+1);  
        return;
    }
    work(son[x]);
}
int main() {
    scanf("%d",&n);
    for(int i=1;i<=n;++i) {
        int x;
        scanf("%d",&x);
        son[i]=x;
    }
    for(int i=1;i<n;++i) {
        if(dis[i]==0) {
            beg=js;
            work(i);
        }
    }
    printf("%d\n",ans);
    return 0;
}

Day2T1

思路:

非常经典的二分答案,二分最短长度,然后进行check,如果挪走的石头比要求的多,就增大最短长度。否则减小最短长度。

代码:

#include<cstdio>
#include<iostream>
using namespace std;
const int N=50000+1000;
int L,n,m;
int a[N];
int check(int x) {
    int last=0;
    int ans=0;
    for(int i=1;i<=n;++i) {
        if(a[i]-last<x)
            ans++;
        else last=a[i];
    }
    return ans;
}
int main() {
    scanf("%d%d%d",&L,&n,&m);
    for(int i=1;i<=n;++i) {
        scanf("%d",&a[i]);
    }
    a[++n]=L;
    int ans=0;
    int l=0,r=L;
    while(l<=r) {
        int mid=l+r>>1;
        if(check(mid)<=m) ans=mid,l=mid+1;
        else r=mid-1;
    }
    printf("%d\n",ans);
    return 0;
}

Day2T2

思路:

用f[i][j][k][0/1]表示a串匹配到了第i个,b串匹配到了第j个,共zhao找了k个子串。一定选当前子串(1)或者可选可不选(0)的方案数。

如果a[i]与b[j]相等的话,那么一定当前字符可以与前面字符组成同一个子串(那么前一个字符必须用到了),或者自己开始一个新的子串(前一个字符可用可不用)。即\(f[i][j][k][1]=f[i-1][j-1][k][1]+f[i-1][j-1][k-1][0]\)

如果a[i]!=b[j]那么当前字符就不能选,所以f[i][j][k][1]=0

最后f[i][j][k][0]=f[i][j][k][1]+f[i-1][j][k][0]

代码:


#include<cstdio>
#include<iostream>
using namespace std;
const int N=1010,M=210,mod=1000000007;
int f[3][N][M][2];
char a[N],b[M];
int main() {
    int n,m,K;
    scanf("%d%d%d",&n,&m,&K);
    scanf("%s%s",a+1,b+1);
    f[0][0][0][0]=1;
    for(int i=1;i<=n;++i) {
        int z=i&1;
        f[z][0][0][0]=1;
        for(int j=1;j<=m;++j) { 
            for(int k=1;k<=K;++k) {
                if(a[i]==b[j])
                    f[z][j][k][1]=(f[z^1][j-1][k-1][0]+f[z^1][j-1][k][1])%mod;
                else
                    f[z][j][k][1]=0;
                f[z][j][k][0]=(f[z][j][k][1]+f[z^1][j][k][0])%mod;
            }
        }
    }
    printf("%d\n",f[n&1][m][K][0]);
    return 0;
}

Day2t3

15分思路:

n和m小于100,那就可以随便搞了啊。枚举删掉的每条边,然后在每次处理出lca,然后对于每个计划都求一下路程,从里面找最优秀的就行了

30分思路:

m==1时,就将该计划中最长的那条边减去就是答案。

65分思路:

如果树退化成了一条链,那么就可以二分答案。二分出最长路径的长度,然后对于比该答案长的计划,就说明该路径中必须被减去一条边使得该路径的长度小于改答案。那么对于所有计划,就是找到一条边,使得所有不满足二分出的这个答案的计划都经过了这条边,而且最长的计划减去该边之后长度小于答案。那么怎么记录每条边被多少个路径经过呢?差分。

100分思路:

与65分思路相同,只是在维护每条边被多少个路径经过时进行树上差分。就是将该路径的起点和终点+1,将LCA-2.然后按照反着的dfs序将每个点的父亲都加上当前点的值就可以得到每个点被经过的次数。

代码:

#include<cstdio>
#include<cstring>
#include<iostream>
using namespace std;
const int N=300000+100,logN=24;
int read() {
    int x=0,f=1; char c=getchar();
    while(!isdigit(c)) {
        if(c=='-') f=-1;
        c=getchar();
    }
    while(isdigit(c)) {
        x=x*10+c-'0';
        c=getchar();
    }
    return x*f;
}
struct node
{
    int v,nxt,w;
}e[N*2];
int ejs,head[N];
void add(int u,int v,int w) {
    e[++ejs].v=v;e[ejs].w=w;e[ejs].nxt=head[u];head[u]=ejs;
}
int n,m,lca[N][logN+5],dep[N],dis[N],p[N],cnt;//p用来记录dfs序,dep表示深度,dis表示从根节点到当前节点的边权之和
int L[N],R[N],Cost[N],fa[N],MAXX,emax;//分别记录每个计划的起点,终点,花费,最近公共祖先
int neww[N];
void dfs(int u,int father) {
    p[++cnt]=u;
    for(int i=1;i<=logN;++i) {
        lca[u][i]=lca[lca[u][i-1]][i-1];
        if(!lca[u][i]) break;
    }
    for(int i=head[u];i;i=e[i].nxt) {
        int v=e[i].v;
        if(v==father) continue;
        dep[v]=dep[u]+1;
        dis[v]=dis[u]+e[i].w;
        lca[v][0]=u;
        neww[v]=e[i].w;
        dfs(v,u);
    }
}
int LCA(int x,int y) {
    if(dep[x]>dep[y]) 
        swap(x,y);
    for(int i=logN-1;i>=0;--i)
        if(dep[lca[y][i]]>=dep[x])
            y=lca[y][i];
    for(int i=logN-1;i>=0;--i)
        if(lca[y][i]!=lca[x][i])
            x=lca[x][i],y=lca[y][i];
    if(x!=y) x=lca[x][0];
    return x;
}
int f[N];
int check(int x) {
    memset(f,0,sizeof(f));
    int js=0;
    for(int i=1;i<=m;++i) {
        if(Cost[i]>x)
            f[L[i]]++,f[R[i]]++,f[fa[i]]-=2,js++;
    }
    for(int i=n;i>=1;--i) {
        f[lca[p[i]][0]]+=f[p[i]];
        if(neww[p[i]]>=MAXX-x&&f[p[i]]==js) return 1;
    }
    return 0;
}
int work() {
    int l=MAXX-emax,r=MAXX,ans;
    while(l<=r) {//二分的是最长链的长度
        int mid=(l+r)>>1;
        if(check(mid)) ans=mid,r=mid-1;
        else l=mid+1;
    }
    return ans;
}
int main() {
    n=read(),m=read();
    for(int i=1;i<n;++i) {
        int x=read(),y=read(),w=read();
        add(x,y,w);
        add(y,x,w);
        emax=max(emax,w);
    }
    dep[1]=1,dep[0]=-1,cnt=0;
    dfs(1,0);
    for(int i=1;i<=m;++i) {
        L[i]=read();R[i]=read();
        fa[i]=LCA(L[i],R[i]);
        Cost[i]=dis[L[i]]+dis[R[i]]-(dis[fa[i]]<<1);
        MAXX=max(MAXX,Cost[i]);
    }
    cout<<work();
    return 0;
}

总结

2015年的题还是比较良心的,毕竟第一天的前两个题和第二天的第一题都是送分题,然后只要随便在抠出5分就能达到省一线了,相比后两年不知道良心到哪里去了。