1. 程式人生 > 其它 >NOIP提高組模擬賽4

NOIP提高組模擬賽4

A. 入陣曲

丹青千秋釀,一醉解愁腸。

無悔少年枉,只願壯志狂。

矩陣字首和加暴力\(O(N^2M^2)\) 60pts有手就行

觀察資料範圍,猜測應該是求一種\(O(N^3)\)的演算法,想到之前做的題,應該是\(N^2\)列舉行,\(N\)處理一個序列的答案,然後,就沒有然後了

對於一個序列,求子段和為k的倍數,如何\(O(N)\)求解,

考慮字首和,如果一個子段和為k的倍數,那麼子段的左右兩界在模k意義下是同餘的,開陣列記錄,\(s[i]\)表示模k餘數為i的字首和有幾個,而任意兩個都對應一個合法的矩陣,所以可以愉快的切掉此題,還有一點就是\(s[0]\)初值應設為1,原因模k為0的直接就是合法矩陣。

記得開longlong,還有不要每次memset,不然你會喜提TLE35 還不如暴力

#include<cstdio>
#include<cstring>

using namespace std;
const int maxn=405;
int n,m,k;
int mp[maxn][maxn];
long long sum[maxn][maxn];
int s[1000005];
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)
        scanf("%d",&mp[i][j]);
    for(int i=1;i<=n;++i)
      for(int j=1;j<=m;++j)
        sum[i][j]+=sum[i-1][j]+sum[i][j-1]-sum[i-1][j-1]+mp[i][j];
    long long ans=0;
    for(int i=1;i<=n;++i)
        for(int x=i;x<=n;++x)
        {
          s[0]=1;
          for(int j=1;j<=m;++j){
            long long w=(sum[x][j]-sum[i-1][j])%k;
            ans+=s[w];s[w]++;
          }
            for(int j=1;j<=m;++j){
              long long w=(sum[x][j]-sum[i-1][j])%k;
              s[w]--;
            }
        }

    printf("%lld\n",ans);
    return 0;
}

B. 將軍令

歷史落在贏家之手,至少我們擁有傳說

誰說敗者無法不朽,拳頭只能讓人低頭

念頭卻能讓人抬頭,抬頭去看去愛去追

你心中的夢

就我一個打DP的,思路沒啥問題,但是掛了一堆細節,調了一個多小時,結果只有20pts,然後改了一點細節A了

畫圖可以發現,轉移時子節點不僅可能對父節點有貢獻,還可能需要父節點貢獻,設\(g[x][i]\)表示以x為根的子樹能夠駐守的距離為i的最小方案,轉移時我們需要知道\(g[v][j]\)(v是x的子節點,\(j \in [-k,k]\)),為了處理下標為負的情況,我統一加上了21,也可以像洛谷題解一樣加k。

考慮具體過程(具體程式碼下標請自行加上一個常數)

如果是葉節點 那麼\(g[x][i]=0\;(i\in [-k,-1]) g[x][i]=1\;(i\in [0,k])\)

一般情況的轉移

預處理\(ls[i]\)記錄x子樹駐守距離為i的方案之和

如果在x駐守,那麼子節點貢獻-k即可\(g[x][k]=ls[-k]+1\)

如果x有正貢獻i,但是沒有在x駐守,那麼一定有一個子節點貢獻了i+1,其他子節點貢獻-i即可\(g[x][i]=min(g[v][i+1]+ls[-i]-g[v][-i])i\in[0,k)\)

如果x有負貢獻i,那麼所有子節點貢獻了i+1才保證合法\(g[x][i]=ls[i+1]i\in[-k,-1]\)

由於某些狀態實際上不存在,所以存在\(g[x][i]>g[x][j](i< j)\)這顯然不合理,所以需要倒著取\(min\)\(g[x][i]=min(g[x][i],g[x][i+1])\),這樣保證了i越小g也越小,且保證合法

dp到這裡就結束了,然後我們再簡單講講正解

考慮放棄DP,這題可以貪心,隨便找個根,然後找最深的點,在該點的k級祖先駐守一定最優,按點深度從大到小排序,每次取最深的點,檢查是否被控制,沒有就駐守k級父親或者根節點,暴力更新周圍點。

附dp程式碼

#include<cstdio>
#include<cstring>

using namespace std;
int min(int x,int y){return x<y?x:y;}
const int maxn=100005;
const int inf=1047483647;
struct edge{
    int to,net;
}e[maxn<<1|1];
int head[maxn],tot,n,k,t;
void add(int u,int v){
    e[++tot].net=head[u];
    head[u]=tot;
    e[tot].to=v;
}
int g[maxn][55];
int ls[55];

void print(){
    puts("");
    for(int i=1;i<=n;++i){
        for(int j=21-k;j<=21+k;++j)
          printf("%d ",g[i][j]);
        puts("");
    }
}
void work(int x,int fx){
    bool flag=1;
    for(int i=head[x];i;i=e[i].net){
        int v=e[i].to;if(v==fx)continue;
        work(v,x);flag=0;   
    }
    if(flag){
        for(int i=21-k;i<21;++i)g[x][i]=0;
        for(int i=21;i<=21+k;i++)g[x][i]=1;
        return;
    }else{
        for(int i=21-k;i<=21+k;++i)ls[i]=0;
        for(int i=head[x];i;i=e[i].net){
            int v=e[i].to;if(v==fx)continue;
            for(int j=21-k;j<=21+k;++j)ls[j]+=g[v][j];
        }
        for(int i=head[x];i;i=e[i].net){
            int v=e[i].to;if(v==fx)continue;
            for(int j=0;j<k;++j)
            g[x][j+21]=min(g[x][j+21],g[v][j+22]+ls[21-j]-g[v][21-j]);
        }
        for(int i=1;i<=k;++i)g[x][21-i]=ls[21-i+1];
        g[x][k+21]=ls[21-k]+1;
        for(int i=k+21-1;i>=21-k;--i)g[x][i]=min(g[x][i],g[x][i+1]);
    }
    //print();
}
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);
    }
    if(k==0){printf("%d\n",n);return 0;}
    else{
        memset(g,1,sizeof(g));  
        work(1,0);
        printf("%d\n",g[1][21]);
    }
    
    return 0;
}

C. 星空

命運偷走如果只留下結果,時間偷走初衷只留下了苦衷。

你來過, 然後你走後, 只留下星空。

這題,挺神的。。。

考場由於調T2根本沒時間打T3,最後輸出2居然騙了12pts

搜尋類似分手是祝願那題,大概有72pts,可惡啊

正解需要億點巧妙轉化

首先用01表示燈亮與滅,然後用類似字首和的方式維護一個字首的差分\(cf[i]=a[i]\;xor\;a[i-1]\),注意i最大要到n+1

每次操作,相當於取反兩個數,最終目的是讓整個序列變成0
這就是


第一個轉化

對於給定的0 1數列,每次對於按要求的某兩個數進行取反,問最少次可以使數列全部變為1.


想到分手那題,或者簡單思考一下,可以發現每次操作一定至少有1個1,如果操作兩個1,那麼他們都變成0,如果操作一個1一個0,那相當於交換他們的位置,1最多16個且位置已知,於是有了


第二個轉化

給你幾個點,每次將其中一個點移動特定的距離,如果兩個點碰到了一起,則兩個點一起消失。

問如何移動使得消去所有的點的步數最少。


所以,跑最短路,我使用了某個死去的演算法,這樣得出消去任意兩個1的最小步數,問題可以進一步轉化


第三個轉化

給你一堆物品,一次只能取出給定的一對物品,取出不同對的物品有不同的代價,問如何取出物品使得代價最小。


物品只有16個,狀壓即可

#include<cstdio>
#include<cstring>
#include<queue>
#include<cmath>
using namespace std;
int min(int x,int y){return x<y?x:y;}
int max(int x,int y){return x>y?x:y;}
int flag[40005],cf[40005];
int n,k,m,op[67],x[19],cnt;
int dis[21][40005];
bool vis[40005];
queue<int>q;

void spfa(int now,int nowx){
    memset(vis,0,sizeof(vis));
    q.push(nowx);dis[now][nowx]=0;
    while(!q.empty()){
        int y=q.front();q.pop();vis[y]=0;
        for(int i=1;i<=m;++i)
        {
            int v=y+op[i],u=y-op[i];
            if(v<=n+1){
                if(dis[now][v]>dis[now][y]+1){
                   dis[now][v]=dis[now][y]+1;
                   if(!vis[v]){q.push(v);vis[v]=1;}
                }
            }
            if(u>0){
                if(dis[now][u]>dis[now][y]+1){
                   dis[now][u]=dis[now][y]+1;
                   if(!vis[u]){q.push(u);vis[u]=1;} 
                }
            }
        }
    }
}

int f[67737];
void dp(){
    memset(f,0x3f,sizeof(f));
    f[(1<<cnt)-1]=0;
    for(int i=(1<<cnt)-1;i;--i){
        for(int j=1;j<=cnt;++j){
            if(!((1<<(j-1))&i))continue;
              for(int k=j+1;k<=cnt;++k){
                  if(!((1<<(k-1))&i))continue;
                  int s=~((~i)|(1<<(j-1))|(1<<(k-1)));
                  f[s]=min(f[s],f[i]+dis[j][x[k]]);
              }
        }
    }
    printf("%d\n",f[0]);
}
int main()
{
    freopen("starlit.in","r",stdin);
    freopen("starlit.out","w",stdout);
    scanf("%d%d%d",&n,&k,&m);
    for(int i=1;i<=k;++i){int a;scanf("%d",&a);flag[a]=1;}
    for(int i=1;i<=m;++i)scanf("%d",&op[i]);
    for(int i=1;i<=n+1;++i)cf[i]=flag[i-1]^flag[i];
    for(int i=1;i<=n+1;++i)if(cf[i])x[++cnt]=i;
    memset(dis,0x3f,sizeof(dis));
    for(int i=1;i<=cnt;++i)spfa(i,x[i]);
    dp();
    return 0;
}