1. 程式人生 > >NOIP2016提高組複賽 解題報告

NOIP2016提高組複賽 解題報告

Day1

T1

按照題意模擬就行了,水。
程式碼:

#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
#define N 100005

int n,m,d,num,now;
char s[N][20];
int len[N],dir[N],pre[N],nxt[N];

int main()
{
    freopen("toy.in","r",stdin);
    freopen("toy.out","w",stdout);
    scanf("%d%d\n"
,&n,&m); for (int i=1;i<=n;++i) { gets(s[i]);len[i]=strlen(s[i]); dir[i]=s[i][0]-'0'; for (int j=1;j<len[i]-1;++j) s[i][j]=s[i][j+1]; } /* for (int i=1;i<=n;++i) { for (int j=1;j<=len[i]-2;++j) putchar(s[i][j]); putchar('\n'); } */
now=1; for (int i=1;i<=m;++i) { scanf("%d%d",&d,&num); if (!d)//left { if (!dir[now])//in { now=((now-num-1)%n+n)%n+1; } else//out { now=(now+num-1)%n+1; } } else
//right { if (!dir[now]) { now=(now+num-1)%n+1; } else { now=((now-num-1)%n+n)%n+1; } } } for (int i=1;i<=len[now]-2;++i) putchar(s[now][i]); putchar('\n'); }

T2

感覺這道題是今年最神的一道題,沒想到放到day1T2來考。
考試的時候一個一個部分分打暴力,不過最後只搞出來了60pts。這題的暴力分很良心,不過每一個都不是很好打。。
正解是樹上差分。題解的前半部分說得很明白:
這裡寫圖片描述
也就是說,將每一個路徑拆成4個,2條到根的和2條從根出發的。
假設一個觀察員在v,其時間為w
如果要統計有多少個從u到根的人可以被v觀察到的話,那麼首先,uv的子樹裡。其次,要保證t+h(u)h(v)=w,即t+h(u)=w+h(v)
如果要統計有多少個從根到u的人可以被v觀察到的話,那麼同樣,uv的子樹裡,並且t+h(v)h(root)=w,即t=wh(v)+h(root)
我們可以把等式前面的看做每一個人的權,把等式後面的看做是每一個觀測員的權。
那麼,設ai表示權為i的人的數量,然後在樹上dfs一遍求出來每一個觀測員子樹中有多少個和它的權相等就可以了。
程式碼:

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
#define N 1000005
#define base 300000
#define sz 19

int n,m,x,y,s,t,r,ct,ft;
struct hp{int pt,t,val;}tor[N*2],fromr[N*2];
int tot,point[N],nxt[N*2],v[N*2];
int father[N],a[N],w[N],dfs_clock,f[N][sz+5],h[N],in[N],ans[N];

void add(int x,int y)
{
    ++tot; nxt[tot]=point[x]; point[x]=tot; v[tot]=y;
}
void build(int x,int fa)
{
    father[x]=fa;h[x]=h[fa]+1;in[x]=++dfs_clock;
    for (int i=1;i<sz;++i)
    {
        if (h[x]-(1<<i)<1) break;
        f[x][i]=f[f[x][i-1]][i-1];
    }
    for (int i=point[x];i;i=nxt[i])
        if (v[i]!=fa)
        {
            f[v[i]][0]=x;
            build(v[i],x);
        }
}
int lca(int x,int y)
{
    if (h[x]<h[y]) swap(x,y);
    int k=h[x]-h[y];
    for (int i=0;i<sz;++i)
        if (k&(1<<i)) x=f[x][i];
    if (x==y) return x;
    for (int i=sz-1;i>=0;--i)
        if (f[x][i]!=f[y][i]) x=f[x][i],y=f[y][i];
    return f[x][0];
}
int cmp(hp a,hp b)
{
    return in[a.pt]<in[b.pt];
}
void dfs_to(int x,int fa)
{
    int val=w[x]+h[x],minus=a[val];
    while (ct<=2*m&&tor[ct].pt==x) a[tor[ct].t+h[tor[ct].pt]]+=tor[ct].val,ct++;
    for (int i=point[x];i;i=nxt[i])
        if (v[i]!=fa)
            dfs_to(v[i],x);
    ans[x]+=a[val]-minus;
}
void dfs_from(int x,int fa)
{
    int val=w[x]-h[x]+1,minus=a[val+base];
    while (ft<=2*m&&fromr[ft].pt==x)
        a[fromr[ft].t+base]+=fromr[ft].val,ft++;
    for (int i=point[x];i;i=nxt[i])
        if (v[i]!=fa)
            dfs_from(v[i],x);
    ans[x]+=a[val+base]-minus;
}
int main()
{
    freopen("running.in","r",stdin);
    freopen("running.out","w",stdout);
    scanf("%d%d",&n,&m);
    for (int i=1;i<n;++i)
    {
        scanf("%d%d",&x,&y);
        add(x,y);add(y,x);
    }
    build(1,0);
    for (int i=1;i<=n;++i) scanf("%d",&w[i]);
    for (int i=1;i<=m;++i)
    {
        scanf("%d%d",&s,&t);
        r=lca(s,t);
        tor[++ct].pt=s,tor[ct].t=0,tor[ct].val=1;
        if(r!=1)tor[++ct].pt=father[r],tor[ct].t=h[s]-h[r]+1,tor[ct].val=-1;
        fromr[++ft].pt=t,fromr[ft].t=h[s]-h[r]-(h[r]-1),fromr[ft].val=1;
        fromr[++ft].pt=r,fromr[ft].t=h[s]-h[r]-(h[r]-1),fromr[ft].val=-1;
    }
    sort(tor+1,tor+ct+1,cmp);ct=1;
    sort(fromr+1,fromr+ft+1,cmp);ft=1;
    dfs_to(1,0);
    memset(a,0,sizeof(a));dfs_from(1,0);
    for (int i=1;i<=n;++i)
    {
        if (i!=1) putchar(' ');
        printf("%d",ans[i]);
    }
}

T3

實際上這道題就是一道水水的dp。(Ta只用了5min就a掉了= =)
可是當時考試的時候沒時間了,暴力都打得手忙腳亂。
首先用floyed求出來兩兩最短路。
f(i,j,0/1)表示前i個課程,一共選了j個申請,第i個0不申請1申請的最小期望。
然後直接dp就可以了。。。
程式碼:

#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
#define N 2005

int n,m,v,e,x,y;
int c[N],d[N];
double z,inf,kk[N],sdis[N],dis[N][N],f[N][N][2],ans;

void floyed()
{
    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);
    scanf("%d%d%d%d",&n,&m,&v,&e);
    for (int i=1;i<=n;++i) scanf("%d",&c[i]);
    for (int i=1;i<=n;++i) scanf("%d",&d[i]);
    for (int i=1;i<=n;++i) scanf("%lf",&kk[i]);
    memset(dis,127,sizeof(dis));inf=dis[0][0];
    for (int i=1;i<=v;++i) dis[i][i]=0.0;
    for (int i=1;i<=e;++i)
    {
        scanf("%d%d%lf",&x,&y,&z);
        dis[x][y]=dis[y][x]=min(dis[x][y],z);
    }
    floyed();memset(f,127,sizeof(f));
    /*
    for (int i=1;i<=v;++i)
        for (int j=i;j<=v;++j)
            printf("%d %d %.2lf\n",i,j,dis[i][j]);
    puts("");
    */
    for (int i=2;i<=n;++i)
        ans+=dis[c[i-1]][c[i]];
    f[1][0][0]=f[1][1][1]=0.0;
    for (int i=1;i<n;++i)
        for (int j=0;j<=min(i,m);++j)
        {
            if (f[i][j][0]!=inf)
            {
                f[i+1][j][0]=min(f[i+1][j][0],f[i][j][0]+dis[c[i]][c[i+1]]);
                if (j<m) f[i+1][j+1][1]=min(f[i+1][j+1][1],f[i][j][0]+dis[c[i]][c[i+1]]*(1-kk[i+1])+dis[c[i]][d[i+1]]*kk[i+1]);
            }
            if (f[i][j][1]!=inf)
            {
                f[i+1][j][0]=min(f[i+1][j][0],f[i][j][1]+dis[c[i]][c[i+1]]*(1-kk[i])+dis[d[i]][c[i+1]]*kk[i]);
                if (j<m) f[i+1][j+1][1]=min(f[i+1][j+1][1],f[i][j][1]+(1-kk[i])*(1-kk[i+1])*dis[c[i]][c[i+1]]+(1-kk[i])*kk[i+1]*dis[c[i]][d[i+1]]+kk[i]*(1-kk[i+1])*dis[d[i]][c[i+1]]+kk[i]*kk[i+1]*dis[d[i]][d[i+1]]);
            }
        }
    for (int i=0;i<=m;++i) ans=min(ans,min(f[n][i][0],f[n][i][1]));
    printf("%0.2lf\n",ans);
}

Day2

T1

實際上就是統計Cji%k==0的數量,二維字首和搞一下就行了。
考試的時候搞了一個非常傻逼的方法,就是將每一個Cjik分解質因數,然後如果相減每一個質因子數都>=0就說明是可以整除的。
這樣的話預處理用一個轉移,就是Cji推到Cj+1i,也就是說i(ij)j推到i(ij1)(j+1)只需要加上i的質因子再減去j的質因子就可以了。
由於k<=21,所以只需要考慮前8個質數就可以了,時間複雜度為O(n28)
程式碼:

#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
#define LL long long

int t,n,m,k,ANS;
int prime[9]={0,2,3,5,7,11,13,17,19};
int p[2005][20],s[2005][20],a[20],b[20],c[20],d[20],ans[2005][2005],Ans[2005][2005];

void calc(int x)
{
    int now=x;if (now==1) return;
    for (int i=1;i<=8;++i)
        if (now%prime[i]==0)
        {
            while (now%prime[i]==0)
                p[x][i]++,now/=prime[i];
            if (now==1) break;
        }
}
void sum(int x)
{
    for (int i=1;i<=8;++i)
        s[x][i]=s[x-1][i]+p[x][i];
}
int main()
{
    freopen("problem.in","r",stdin);
    freopen("problem.out","w",stdout);
    scanf("%d%d",&t,&k);
    for (int i=1;i<=2000;++i)
        calc(i);
    for (int i=1;i<=2000;++i) sum(i);
    for (n=0;n<=2000;++n)
    {
        bool flag=true;
        for (int i=1;i<=8;++i) a[i]=s[n][i],b[i]=s[n][i],c[i]=0,d[i]=a[i]-b[i]-c[i]-p[k][i];
        for (int i=1;i<=8;++i)
        {
            if (d[i]<0) {flag=false;break;}
        }
        if (flag) ans[n][0]++;
        for (m=1;m<=n;++m)
        {
            bool flag=true;
            for (int i=1;i<=8;++i) b[i]-=p[n-m+1][i],c[i]+=p[m][i],d[i]=a[i]-b[i]-c[i]-p[k][i];
            for (int i=1;i<=8;++i)
            {
                if (d[i]<0) {flag=false;break;}
            }
            if (flag) ans[n][m]++;
        }
    }
    for (int i=0;i<=2000;++i) Ans[i][0]=ans[i][0],Ans[0][i]=ans[0][i];
    for (int i=1;i<=2000;++i)
        for (int j=1;j<=2000;++j) Ans[i][j]=Ans[i-1][j]+Ans[i][j-1]-Ans[i-1][j-1]+ans[i][j];
    while (t--)
    {
        scanf("%d%d",&n,&m);
        printf("%d\n",Ans[n][m]);
    }
    return 0;
}

T2

搞三個數列。讀進來初始值了之後排序,然後從大到小放到第一個佇列裡。每一次取3個佇列中隊首最大的那個值,然後計算出來pxxpx之後分別將這兩個數放到第2個和第3個佇列裡。
可以發現這樣的話這三個佇列都是分別單調的。
由於每一次要將其餘的都+q,那麼就將新得到的兩個值-q之後再加入佇列就可以了。
看起來非常科學非常簡單的方法,但是考試的時候為什麼想不到呢?
程式碼:

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<queue>
#include<cmath>
using namespace std;
#define inf 2100000000
#define N 100005

int n,m,t,x,qaq,add,a[N];
double u,v,p;
int q[4][N*100],l[4],r[4];

int cmp(int a,int b)
{
    return a>b;
}
int Max()
{
    int a=-inf,b=-inf,c=-inf,ans=1;
    if (l[1]<r[1]) a=q[1][l[1]];
    if (l[2]<r[2]) b=q[2][l[2]];
    if (l[3]<r[3]) c=q[3][l[3]];
    if (a<b) a=b,ans=2;
    if (a<c) a=c,ans=3;
    return ans;
}
int main()
{
    freopen("earthworm.in","r",stdin);
    freopen("earthworm.out","w",stdout);
    scanf("%d%d%d%lf%lf%d",&n,&m,&qaq,&u,&v,&t);p=u/v;
    for (int i=1;i<=n;++i) scanf("%d",&a[i]);
    sort(a+1,a+n+1,cmp);
    for (int i=1;i<=n;++i) q[1][r[1]++]=a[i];
    for (int i=1;i<=m;++i)
    {
        int k=Max();
        int now=q[k][l[k]++]+add;
        if (i%t==0)
        {
            if (i>t) putchar(' ');
            printf("%d",now);
        }
        int nxt1=floor(p*(double)now),nxt2=now-floor(p*(double)now);
        add+=qaq;
        q[2][r[2]++]=nxt1-add,q[3][r[3]++]=nxt2-add;
    }
    putchar('\n');
    for (int i=1;i<=n+m;++i)
    {
        int k=Max();
        int now=q[k][l[k]++]+add;
        if (i%t==0)
        {
            if (i>t) putchar(' ');
            printf("%d",now);
        }
    }
}

相關推薦

NOIP2016提高複賽 解題報告

Day1 T1 按照題意模擬就行了,水。 程式碼: #include<iostream> #include<cstring> #include<cstdio> using namespace std; #defin

NOIP2015提高複賽 解題報告

Day 1 T1  直接根據題目描述的模擬就可以了,水。 【程式碼】 #include<iostream> #include<cstring> #include<cstdio> using namespace std; int n,m,i

NOIP2013 提高複賽解題報告

NOIP2013 提高組複賽 day1 1002. 火柴排隊 貪心+資料結構/歸併排序 這個“相鄰交換”讓我聯想到了NOIP2012_day1_task2_game那題的噁心做法,於是就專注推導相鄰兩個元素交換對解的影響。然後根據以前經

NOIP2016普及複賽解題報告

買鉛筆 【題目描述】 P老師需要去商店買n支鉛筆作為小朋友們參加NOIP的禮物。她發現商店一共有 3種包裝的鉛筆,不同包裝內的鉛筆數量有可能不同,價格也有可能不同。為了公平起 見,P老師決定只買同一種包裝的鉛筆。 商店不允許將鉛筆的包裝拆開,因此P老師可能需要購買超過n支鉛筆才夠給小朋 友們發禮物。 現在P老

CCF全國資訊學奧林匹克聯賽(NOIP2016複賽模擬提高 day2 解題報告

從昨天開始就進入了一天一考的可怕時間。 還有17天就考試了啊。 CCF全國資訊學奧林匹克聯賽 (NOIP2016) 複賽模擬提高組 day2 解題報告 1. Sequence Limits Time: 1000ms per test c

NOIP2018提高Day2 解題報告

前言 關於\(NOIP2018\),詳見此部落格:NOIP2018學軍中學遊記(11.09~11.11)。 \(Day2\)的題目和\(Day1\)比起來,真的是難了很多啊。 \(T1\):旅行(點此看題面) 對於樹的情況,顯然可以把相鄰的點全部存下來,排序一遍後依次遍歷即可。 對於基環外向樹的

2017年第23屆全國青少年資訊學奧林匹克競賽分割槽聯賽提高複賽題解報告

2017年第23屆全國青少年資訊學奧林匹克競賽分割槽聯賽複賽提高組題解報告 山西現代雙語學校南校 劉鍇睿 Day1 第一題:小凱的疑惑 這道題拿上以後,就很悶,十幾年的送分模擬題,成了數學題,考場上蒙B的我沒思路就先拿樣例手動模

2008年NOIP普及複賽解題報告

2008年NOIP普及組解題報告 王祺磊 (本份解題報告以C++為參考程式) 一、ISBN號碼 一道讓人很長知識的題目,但是題目所蘊含的解題方法,卻十分直接。你甚至可以不用一個迴圈就可以搞定這道題。因為每一位都是固定的。到底第幾位乘幾都是固定的。在這道題中只要控制好字串的位數,就可以

NOIP2017普及複賽解題報告

這次考試在衢州二中,這裡地形太複雜了,離開考前3分鐘才找到考場。無語·········所以一開考就沒有好心情。這次的題目相對來以往的前幾道題比較簡單,但最後一道題比較難,考場上只想出來了50分的程式碼。 時間限制:1s

NOIP2012普及複賽解題報告

【問題描述】 1.質因數分解 (prime.cpp/c/pas) 已知正整數 n 是兩個不同的質數的乘積,試求出較大的那個質數。 【輸入】 輸入檔名為 prime.in。 輸入只有一行,包含一個正整數 n。 【輸出】 輸出檔名為 prime.out。 輸出只有一行

NOIP2016提高複賽】憤怒的小鳥

Description Solution 很明顯是一個狀態壓縮DP的題目,不過也可以打成記憶化搜尋。 設f[i]表示每個點選或不選的狀態投射小鳥的最少方案。 首先預處理一個g[i][j]表示

NOIP2013提高Day1 解題報告

總的來說 NOIP2013Day1的這三道題,感覺總的來說還是並不難,並且也比較符合套路。T1是良心的一眼送分題,T2是比較需要思考,程式碼寫起來比較簡單的但建立模型需要好好思考的偏重演算法思維的題目,T3是比較注重程式碼能力,程式碼量稍大(還是隻是因為我

NOIP2016提高】憤怒的小鳥(狀壓寬搜)

數組保存 結果 4.0 2.0 pac 之前 第一個 ... 預處理 題目描述 Kiana最近沈迷於一款神奇的遊戲無法自拔。 簡單來說,這款遊戲是在一個平面上進行的。 有一架彈弓位於(0,0)處,每次Kiana可以用它向第一象限發射一只紅色的小鳥,小鳥們的飛行軌跡均為形如的

洛谷 P2827 蚯蚓(NOIp2016提高D2T2)

mat 希望 c++ ffffff define fff 空行 特殊 到來 題目描述 本題中,我們將用符號?c?表示對c向下取整,例如:?3.0?=?3.1?=?3.9?=3。 蛐蛐國最近蚯蚓成災了!隔壁跳蚤國的跳蚤也拿蚯蚓們沒辦法,蛐蛐國王只好去請神刀手來幫他們消滅蚯蚓。

[NOIP2016提高]合數問題

() -a printf ++ 提高 urn 數論 tro ont 題目:UOJ#263、洛谷P2822、Vijos P2006、codevs5947。 題目大意:t組數據,每次給你n和m$\leq 2000$,求對於所有的$(0\leq i\leq n)$,$(0\le

NOIP2016提高】換教室

algorithm .org pan i++ mes ble 安排 turn 路徑 https://www.luogu.org/problem/show?pid=1850 題面很長,實質很水的一道期望DP題。題面自帶勸退效果。 首先用Floyd算出任意兩點的最短路徑。然後設

[NOIP2016提高]憤怒的小鳥

noi 二維 枚舉 def 拋物線 ++ scanf pre sizeof 題目:UOJ#265、洛谷P2831、Vijos P2008。 題目大意:有n頭豬,都在一個二維坐標系裏(每頭豬坐標為兩位小數)。規定每只鳥能從(0,0)處發射,且經過的拋物線一定為$y=ax^2

NOIP2016提高初賽(2)四、閱讀程序寫結果2、

cal 當前 閱讀 輸入 自己 字符 細節 一是 表示 #include <iostream> using namespace std; int main() { char a[100][100], b[100][100]; string c[100]; s

NOIP2016提高初賽(2)四、讀程序寫結果3、求最長回文子序列

所有 並且 names mes font esp mic abcd 大小 #include <iostream> using namespace std; int lps(string seq, int i, int j) { int len1, len2;

NOIP2016提高】 Day1 T3 換教室

scanf spa -s 提高 i++ double -1 教室 pan 題目鏈接:https://www.luogu.org/problemnew/show/P1850 此題正解為dp。 我們先用floyd處理出任意兩個教室之間的距離,用dis[i][j]表示。