NOIP2016提高组Day1题解

前言

(我去年还是普及组蒟蒻呢……)
最近模拟考了一下NOIP2016提高组Day1,结果只搞了156分……太弱了。
T1:100分 T2:40分 T3:16分
其中T3的v打成n,然后就炸掉了,本来可以80(然后就上200了啊,T_T)。

玩具谜题

解题报告

水题,应该是用来送分的吧。只需要注意越界时候的处理就行了。
不过如果使用字符数组,要多开一个用来存’\0’,否则就会炸掉。

示例程序

#include<cstdio>
using namespace std;
const int maxn=100000;
const int fl[2][2]={{-1,1},{1,-1}};

int n,te,pos;
struct Toy {int f;char s[11];};
Toy a[maxn+5];

bool Eoln(char ch) {return ch==10||ch==13||ch==EOF;}
int readi(int &x)
{
    int tot=0,f=1;char ch=getchar(),lst='+';
    while ('9'<ch||ch<'0') {if (ch==EOF) return EOF;lst=ch;ch=getchar();}
    if (lst=='-') f=-f;
    while ('0'<=ch&&ch<='9') tot=tot*10+ch-48,ch=getchar();
    x=tot*f;
    return Eoln(ch);
}
int reads(char *s)
{
    int len=0;char ch=getchar();if (ch==EOF) return EOF;
    while ('z'<ch||ch<'a') ch=getchar();
    while ('a'<=ch&&ch<='z') s[len++]=ch,ch=getchar();s[len]=0;
    return len;
}
int main()
{
    freopen("toy.in","r",stdin);
    freopen("toy.out","w",stdout);
    readi(n);readi(te);pos=0;
    for (int i=0;i<n;i++) readi(a[i].f),reads(a[i].s);
    while (te--)
    {
        int f,s;readi(f);readi(s);
        pos+=fl[a[pos].f][f]*s;
        if (pos<0) pos+=n;if (pos>=n) pos-=n;
    }
    printf("%s\n",a[pos].s);
    return 0;
}

天天爱跑步

解题报告

(乘机水掉BZOJ4719)
好像是Day1(和Day2?)最难的题目。

处理子问题的时候我们会发现很多暗示:比如链的时候满足观察员x的节点只能为x+w[x]或x-w[x],S=1和T=1实际上在告诉我们把S->T分一下。
结合一下,我们有这么一个思路:把S->T分一下,不要直接看成S->T,且把满足的节点转化为代数式。

S->T怎么分呢?分成S->LCA和LCA->T比较好,因为这样分就得到了两条直链,没有拐弯。再来看满足节点的转化。
1.路径S->LCA上的满足节点x
不难得出,当dep[S]-dep[x]=w[x](且LCA不在x下面)时,从S出发能恰好到x,移项得到dep[S]=dep[x]+w[x]。
2.路径LCA->T上的满足节点x
这个其实也是很简单的,记S->T的长度为dis,那么当dep[T]-dep [x]=dis-w[x](且LCA不在x下面)时,从S出发能经过LCA并恰好到x,移项得到dep[T]-dis=dep[x]-w[x]。

所以我们可以记录hash[k]表示k这个值出现了几次(有负数向右移即可),然后Dfs处理。以S->LCA为例,对于节点x,先处理x的儿子的子树,遇到S就hash[dep[S]]++,返回来遇到S的LCA就hash[dep[S]]–。最终返回到x时,就得到了当前的hash[k]数组,此时统计hash[dep[x]+w[x]]就行了?其实是不行的,因为有不符合x的节点,也就是LCA在x上面的节点。这怎么办?我们可以记录lst表示刚遇到x时的hash[dep[x]+w[x]],然后处理完x的儿子的子树后hash[dep[x]+w[x]]-lst就是满足的答案了(容斥大法好)。而对于LCA->T也是一样的。

然而到这里并没有结束,这个算法还有个小Bug,就是由于处理的时候处理了S->LCA和LCA->T,LCA被处理了两次!有两种解决方法,第一种就是最后去下重(如果LCA在S->LCA和LCA->t里都是满足节点,就把答案-1),第二种就是刚开始拆路径的时候就不把LCA拆两次。

我的程序好像不是特别快,果然还是太弱了……

示例程序

#include<cstdio>
#include<cmath>
#include<vector>
using namespace std;
const int maxn=299998,maxm=299998,maxt=19;

int n,m,w[maxn+5],st[maxm+5],gl[maxm+5],lca[maxm+5],ans[maxn+5];
int f[maxn+5][maxt+5],dep[maxn+5],ha[3*maxn+5];
int E,lnk[maxn+5],son[2*maxn+5],nxt[2*maxn+5];
struct AdjList
{
    int E,lnk[maxn+5],son[maxn+5],nxt[maxn+5];
    void Add(int x,int y) {son[++E]=y;nxt[E]=lnk[x];lnk[x]=E;}
};
AdjList A,B,C,D;

bool Eoln(char ch) {return ch==10||ch==13||ch==EOF;}
int readi(int &x)
{
    int tot=0,f=1;char ch=getchar(),lst='+';
    while ('9'<ch||ch<'0') {if (ch==EOF) return EOF;lst=ch;ch=getchar();}
    if (lst=='-') f=-f;
    while ('0'<=ch&&ch<='9') tot=tot*10+ch-48,ch=getchar();
    x=tot*f;
    return Eoln(ch);
}
void Add(int x,int y) {son[++E]=y;nxt[E]=lnk[x];lnk[x]=E;}
void Dfs(int x,int fa)
{
    for (int j=lnk[x];j;j=nxt[j])
        if (son[j]!=fa)
        {
            f[son[j]][0]=x;dep[son[j]]=dep[x]+1;
            Dfs(son[j],x);
        }
}
void make_f()
{
    int k=log2(n);
    for (int j=1;j<=k;j++)
    for (int i=1;i<=n;i++)
        f[i][j]=f[f[i][j-1]][j-1];
}
int LCA(int x,int y)
{
    if (dep[x]<dep[y]) swap(x,y);
    for (int i=maxt;i>=0;i--) if (dep[f[x][i]]>=dep[y]) x=f[x][i];
    if (x==y) return x;
    for (int i=maxt;i>=0;i--) if (f[x][i]!=f[y][i]) x=f[x][i],y=f[y][i];
    return f[x][0];
}
int getr(int x) {return x+maxn;}
void Count1(int x,int fa)
{
    int lst=ha[getr(dep[x]+w[x])];
    for (int j=lnk[x];j;j=nxt[j])
        if (son[j]!=fa) Count1(son[j],x);
    for (int j=A.lnk[x];j;j=A.nxt[j]) ha[getr(A.son[j])]++;
    ans[x]+=ha[getr(dep[x]+w[x])]-lst;
    for (int j=C.lnk[x];j;j=C.nxt[j]) ha[getr(C.son[j])]--;
}
void Count2(int x,int fa)
{
    int lst=ha[getr(dep[x]-w[x])];
    for (int j=lnk[x];j;j=nxt[j])
        if (son[j]!=fa) Count2(son[j],x);
    for (int j=B.lnk[x];j;j=B.nxt[j]) ha[getr(B.son[j])]++;
    ans[x]+=ha[getr(dep[x]-w[x])]-lst;
    for (int j=D.lnk[x];j;j=D.nxt[j]) ha[getr(D.son[j])]--;
}
int main()
{
    freopen("running.in","r",stdin);
    freopen("running.out","w",stdout);
    readi(n);readi(m);
    for (int i=1;i<=n-1;i++)
    {
        int x,y;readi(x);readi(y);
        Add(x,y);Add(y,x);
    }
    for (int i=1;i<=n;i++) readi(w[i]);
    dep[1]=1;Dfs(1,-1);make_f();
    for (int i=1;i<=m;i++)
    {
        int dis;readi(st[i]);readi(gl[i]);
        lca[i]=LCA(st[i],gl[i]);dis=dep[st[i]]+dep[gl[i]]-2*dep[lca[i]];
        A.Add(st[i],dep[st[i]]);B.Add(gl[i],dep[gl[i]]-dis);
        C.Add(lca[i],dep[st[i]]);D.Add(lca[i],dep[gl[i]]-dis);
    }
    Count1(1,-1);Count2(1,-1);
    for (int i=1;i<=m;i++) if (dep[st[i]]-dep[lca[i]]==w[lca[i]]) ans[lca[i]]--;
    for (int i=1;i<=n;i++)
        if (i==n) printf("%d\n",ans[i]); else printf("%d ",ans[i]);
    return 0;
}

换教室

解题报告

(乘机水掉BZOJ4720)
这道题其实是不难的期望DP,但是我太弱了,不知道期望的线性性,于是感觉DP完全没法写,打暴力去了(暴力还打挂,蒟蒻+=∞)。
定义f[i][j][0/1]表示前i个时间段,选了j次变更,其中第i次没变更(0)或变更(1)了。
转移方程很容易,但利用了期望的线性性。设c代表原先教室,d代表更换教室,dXY表示i-1的教室为X,i的教室为Y,X->Y的路程,P1表示i-1更换成功的概率,P2表示i更换成功的概率。则:

f[i][j][0]=min(A,B);
A=f[i-1][j][0]+dcc;
//i-1和i都不变更,概率为100%
B=f[i-1][j][1]+P1*ddc+(1-P1)*dcc;
//i-1变更,成功或失败都要算

f[i][j][1]=min(A,B);
A=f[i-1][j-1][0]+P2*dcd+(1-P2)*dcc;
//i变更,成功或失败都要算
B=f[i-1][j-1][1]+P2*(P1*ddd+(1-P1)*dcd)+(1-P2)*(P1*ddc+(1-P1)*dcc);
//i-1和i都变更,成功或失败都要算

示例程序

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn=2000,maxv=300;

int n,m,v,e,C[maxn+5],D[maxn+5];
double K[maxn+5],ans=1e100,f[maxn+5][maxn+5][2];
int dis[maxv+5][maxv+5];

bool Eoln(char ch) {return ch==10||ch==13||ch==EOF;}
int readi(int &x)
{
    int tot=0,f=1;char ch=getchar(),lst='+';
    while ('9'<ch||ch<'0') {if (ch==EOF) return EOF;lst=ch;ch=getchar();}
    if (lst=='-') f=-f;
    while ('0'<=ch&&ch<='9') tot=tot*10+ch-48,ch=getchar();
    x=tot*f;
    return Eoln(ch);
}
int readd(double &x)
{
    double tot=0,f=1,Base=1;char ch=getchar(),lst='+';
    while ('9'<ch||ch<'0') {if (ch==EOF) return EOF;lst=ch;ch=getchar();}
    if (lst=='-') f=-f;
    while ('0'<=ch&&ch<='9') tot=tot*10+ch-48,ch=getchar();
    if (ch=='.')
    {
        ch=getchar();
        while ('0'<=ch&&ch<='9') tot+=(ch-48)/(Base*=10),ch=getchar();
    }
    x=tot*f;
    return Eoln(ch);
}
void Floyd()
{
    for (int k=1;k<=v;k++)
    for (int i=1;i<=v;i++)
    for (int j=1;j<=v;j++)
        dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j]);
}
int main()
{
    freopen("classroom.in","r",stdin);
    freopen("classroom.out","w",stdout);
    readi(n);readi(m);readi(v);readi(e);
    for (int i=1;i<=n;i++) readi(C[i]);
    for (int i=1;i<=n;i++) readi(D[i]);
    for (int i=1;i<=n;i++) readd(K[i]);
    memset(dis,63,sizeof(dis));
    for (int i=1;i<=v;i++) dis[0][i]=0;
    for (int i=1;i<=v;i++) dis[i][i]=0;
    for (int i=1;i<=e;i++)
    {
        int x,y,z;readi(x);readi(y);readi(z);
        dis[x][y]=min(dis[x][y],z);dis[y][x]=min(dis[y][x],z);
    }
    Floyd();memset(f,127,sizeof(f));
    double INF=f[0][0][0];f[0][0][0]=0;
    for (int i=1;i<=n;i++)
    {
        f[i][0][0]=f[i-1][0][0]+dis[C[i-1]][C[i]];
        for (int j=1;j<=min(i,m);j++)
        {
            double A,B;
            A=f[i-1][j][0]+dis[C[i-1]][C[i]];
            B=f[i-1][j][1]+K[i-1]*dis[D[i-1]][C[i]]+(1-K[i-1])*dis[C[i-1]][C[i]];
            f[i][j][0]=min(A,B);
            A=f[i-1][j-1][0]+K[i]*dis[C[i-1]][D[i]]+(1-K[i])*dis[C[i-1]][C[i]];
            B=f[i-1][j-1][1]+K[i]*(K[i-1]*dis[D[i-1]][D[i]]+(1-K[i-1])*dis[C[i-1]][D[i]])+(1-K[i])*(K[i-1]*dis[D[i-1]][C[i]]+(1-K[i-1])*dis[C[i-1]][C[i]]);
            f[i][j][1]=min(A,B);
        }
    }
    for (int j=0;j<=m;j++) ans=min(ans,min(f[n][j][0],f[n][j][1]));
    printf("%.2lf\n",ans);
    return 0;
}
  • 4
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值