1. 程式人生 > >NOIP2016提高組Day1題解

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;
}