1. 程式人生 > >NOIP 2017.10.24 總結+心得

NOIP 2017.10.24 總結+心得

這裡寫圖片描述
世界真的很大
今天的考試厄運纏身,orz
好端端的224變成了137,NOIP要真考成這樣恐怕是要退役了
一些小錯誤和細節處理的問題,實在是。。
儘量通過模擬賽吧自己的問題測出來再即使修改
免得NOIP真的面臨退役的命運233

看題先:

1。

這裡寫圖片描述
講道理NOIP DAY T1這個難度?反正我是不信
其實不算太難,但是真的有點打腦子
心路歷程如下:
是K的倍數?想到NOIP 2016 DAY2 T1,即是說modK為0
那麼處理矩陣字首和?不行這道題可以有中間的矩陣
K不是1e9,是1e6?擺明了有文章
要麼是資料結構維護值域要麼就是直接開一個值域陣列了
400的範圍啊,n^4大暴力,n^3能過,那就是說列舉矩形的某個邊界,然後根據那個陣列的答案?
陣列記錄的是值域,而且是K以內的值,八成就是mod K之後的值。
考慮一個區域modK的值為0,就是說前後兩個矩形的mod值相同!
那麼用陣列記錄一下每個mod值出現過幾次就好了

然後我天才的在每一個n^2後面加了個1e6的memset,正解被卡成暴力orz,得分100變成35orz

正解就是我這個方法了
完整程式碼:

#include<stdio.h>
#include<cstring>
using namespace std;
typedef long long dnt;

int n,m,K;
int src[2000010],sum[500][500],mrk[500];
dnt ans=0;

dnt fix(dnt x)
{
    return (x%K+K)%K;
}

int main()
{
    freopen("rally.in","r"
,stdin); freopen("rally.out","w",stdout); scanf("%d%d%d",&n,&m,&K); for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) { int tmp; scanf("%d",&tmp); sum[i][j]=fix(sum[i-1][j]+sum[i][j-1]+tmp-sum[i-1][j-1]); } for
(int i=1;i<=n;i++) for(int j=i;j<=n;j++) { src[0]=1; for(int k=1;k<=m;k++) { int tmp=fix(sum[j][k]-sum[i-1][k]); mrk[k]=tmp; ans+=src[tmp],src[tmp]++; } for(int k=1;k<=m;k++) src[mrk[k]]=0; } printf("%I64d\n",ans); return 0; } /* 2 3 2 1 2 1 2 1 2 */

感覺考試的時候,如果一道題一開始沒什麼思路,嘗試把題目得到的資訊全部寫下來,寫到紙上,看看能得出哪些二級結論,一步步擴大已知範圍。
然後就是時間複雜度一定要慎重,memset之類的位置一定要特別注意,memset的複雜度是n不是1

2。

這裡寫圖片描述
這道題一開始的思路就是貪心
因為首先想到在葉節點直接放肯定不優
肯定是能不放,就不放
關鍵是怎麼判斷放不放,就是說怎麼判斷不得不放的時候
如果直接從葉節點往上跳K的距離會出問題
那麼就想到記錄一個點離他最遠的沒有覆蓋的點的距離
每往上跳一步,距離就++
但是這樣在子樹合併的時候就會出事,因為一個點放了軍隊之後,對於上面的點也有覆蓋能力
所以再記錄一個點往上跳的最大距離,每往上走一步就取最值然後–
記錄一個點有沒有被覆蓋過,為了防止根節點漏判
大概思路就是這樣
但是具體實現是有不對的地方
考試時也覺得不對,但是稍微一改就過不了樣例,而且自己測了很多組資料都是對的,所以不太敢改,就這麼交了
然後就只有70分

正解是什麼我不知道
反正我最後A了,調的很不容易,模擬了100的樣例,多謝LKQ的圖解
發現我的down陣列處理的其實很模糊,當時就覺得“差不多好像是這樣”就寫上去了
心一橫就改了狀態,只有-1,0,和正數,分別表示這個點被覆蓋,不被覆蓋,和最遠點的距離
明確定義之後就容易多了

完整程式碼:

#include<stdio.h>
#include<algorithm>
#include<cstring>
using namespace std;

const int INF=0x3f3f3f3f;

struct edge
{
    int v,last;
}ed[10000010];

int n,K,t,num=0,ans=0;
int head[5000010],down[5000010],up[5000010],mrk[5000010];

void add(int u,int v)
{
    num++;
    ed[num].v=v;
    ed[num].last=head[u];
    head[u]=num;
}

void dfs(int u,int f)
{
    int tmp1=-INF,tmp2=-INF,tmp3=1,tmp4=0;
    for(int i=head[u];i;i=ed[i].last)
    {
        int v=ed[i].v;
        if(v==f) continue ;
        dfs(v,u);
        tmp3*=(down[v]==-1),tmp4=1;
        tmp1=max(tmp1,down[v]);
        tmp2=max(tmp2,up[v]);
        if(up[v]>0) mrk[u]=1;
    }
    if(tmp1==-INF) down[u]=0;
    else down[u]=tmp1+1;
    if(tmp2==-INF) up[u]=0;
    else up[u]=tmp2-1;
    if(tmp3 && tmp4 && mrk[u]) down[u]=-1;
    if(up[u]>=down[u] && up[u]>0 ) down[u]=-1;
    if(down[u]>=K) ans++,up[u]=K,down[u]=-1,mrk[u]=1;
}

int main()
{
    freopen("general.in","r",stdin);
    freopen("general.out","w",stdout);
    scanf("%d%d%d",&n,&K,&t);
    for(int i=1;i<n;i++)
    {
        int u,v;
        scanf("%d%d",&u,&v);
        add(u,v),add(v,u);
    }
    dfs(1,1);
    if(!mrk[1]) ans++;
    printf("%d\n",ans);
    return 0;
}

哎,這樣算是死在驗證程式碼上的吧
自己覺得好像還可以就馬馬虎虎了事
感覺考試時一定要時刻持懷疑態度,稍有不對就要深究,一定要完全明確才行
想到稍有不對的地方要有直接動手實現和模擬的決斷
這樣

3。

這裡寫圖片描述
這道題考試的時候是真的沒有什麼思路
只是想了一遍狀壓的大模擬,暴力
5分鐘寫完+對拍
確認無誤後24分穩穩到手
後面的幾個答案小於4的點rand的了8分,總分32

正解略有複雜,但想通了還是很簡單
我們把原01串兩兩異或得到一個新的差分數列
原數列的區間翻轉其實就是新數列兩點翻轉
01翻轉考慮成1走到0的位置
不會出現00翻轉的情況,因為是白忙活
11翻轉考慮成一個1走到另一個1的位置,兩個1碰上然後一起消失
兩點翻轉其實就是一個1走到一個與他相距為bi的地方
可以預處理出來每個1走到每個1最少要走多少步,即翻轉多少次
那麼問題就變成了一共有K個1,兩兩之間有一個代價(步數)
兩兩之間碰到會消失
問最少代價使得所有的1全部消失
K的範圍很小,列舉狀態就變成一個簡單狀壓DP了
列舉狀態列舉想要相遇的兩個點,刷表法更新答案
O(k^2 * 2^k)

完整程式碼:

#include<stdio.h>
#include<map>
#include<cstring>
#include<queue>
#include<algorithm>
using namespace std;

const int INF=0x3f3f3f3f;

pair <int,int> p[30];
queue <int> state;

int n,m,K,cnt=0;
int dis[20][100010],a[100010],f[100010],b[100010];

void sov1()
{   
    int ste=(1<<n)-1,ans=ste;
    for(int i=1;i<=K;i++)
    {
        int tmp;
        scanf("%d",&tmp);
        ans^=(1<<tmp-1);
    }
    for(int i=1;i<=m;i++) scanf("%d",&b[i]);
    memset(f,0x3f3f3f3f,sizeof(f));
    f[ste]=0;
    for(int i=ste;i>=0;i--)
        for(int j=1;j<=m;j++)
            for(int k=0;k<n-b[j]+1;k++)
            {
                int tmp=i^(((1<<b[j])-1)<<k);
                f[tmp]=min(f[tmp],f[i]+1);
            }
    printf("%d\n",f[ans]);
}

void SPFA(pair <int,int> S)
{
    int x0=S.first,y0=S.second;
    memset(dis[x0],INF,sizeof(dis[x0]));
    while(!state.empty()) state.pop();
    state.push(y0),dis[x0][y0]=0;
    while(!state.empty())
    {
        int u=state.front();
        state.pop();
        for(int i=1;i<=m;i++)
        {
            if(u+b[i]<=n && dis[x0][u+b[i]]>dis[x0][u]+1)
            {
                dis[x0][u+b[i]]=dis[x0][u]+1;
                state.push(u+b[i]);
            }
            if(u-b[i]>=0 && dis[x0][u-b[i]]>dis[x0][u]+1)
            {
                dis[x0][u-b[i]]=dis[x0][u]+1;
                state.push(u-b[i]);
            }
        }
    }
}

int sov(int ste)
{
    f[ste]=0;
    for(int i=ste;i>=0;i--)
        for(int j=0;j<cnt;j++)
        if(i&(1<<j))
            for(int k=0;k<cnt;k++)
                if(j!=k && i&(1<<k)) 
                    f[i^(1<<j)^(1<<k)]=min(f[i^(1<<j)^(1<<k)],f[i]+dis[j][p[k].second]);
    return f[0];
}

void sov2()
{
    for(int i=1;i<=K;i++)
    {
        int tmp;
        scanf("%d",&tmp);
        a[tmp]=1;
    }
    for(int i=1;i<=m;i++)
        scanf("%d",&b[i]);
    for(int i=0;i<=n;i++)
        if(a[i] ^ a[i+1]) p[cnt]=make_pair(cnt,i),cnt++;
    for(int i=0;i<cnt;i++)
        SPFA(p[i]);
    memset(f,INF,sizeof(f));
    int ans=sov((1<<cnt)-1);
    printf("%d\n",ans);
}

int main()
{
    freopen("starlit.in","r",stdin);
    freopen("starlit.out","w",stdout);
    scanf("%d%d%d",&n,&K,&m);
    if(n<=16) sov1();
    else sov2();
    return 0;
}

這道題算是提供了一步區間轉化為單點的差分思路
先看題目資料範圍考慮對什麼東西狀壓,然後考慮轉化思路

今天的考試實在是很迷
要不是第一題腦殘加了個memset,第二題寫的時候腦子不清楚(。。。)不會這麼慘
本來是信心題考成這樣,我還是太弱啊
葉正好借這幾次模擬考試查出自己易犯的錯誤然後改正,調整自己考試的狀態,深化考試的套路
明天休閒字串考試,要翻盤才好啊

嗯,就是這樣