BZOJ(本校) 2665 密碼鎖 - 思維&dp

Time Limit: 1s
Memory Limit: 512MB
【題目描述】
從前有一把密碼鎖,由N個開關組成。一開始的時候,所有開關都是關上的。當且僅當開關x1,x2,x3,…xk爲開,其他開關爲關時,密碼鎖纔會打開。
你可以進行M種的操作,每種操作有一個size[i],表示,假如你選擇了第i種的操作的話,你可以任意選擇連續的size[i]個格子,把它們全部取反。
你的任務很簡單,最少需要多少步才能打開密碼鎖,或者如果無解的話,請輸出-1。

【輸入格式】
第1行,三個正整數N,K,M,如題目所述。
第2行,K個正整數,表示開關x1,x2,x3..xk必須爲開,保證x兩兩不同。
第三行,M個正整數,表示size[i],size[]可能有重複元素。
【輸出格式】
輸出答案,無解輸出-1。

【樣例輸入1】
10 8 2
1 2 3 5 6 7 8 9
3 5
【樣例輸出1】
2
【樣例輸入2】
3 2 1
1 2
3
【樣例輸出2】
-1

【數據規模與約定】
對於50%的數據,1≤N≤20,1≤k≤5,1≤m≤3;
對於另外20%的數據,1≤N≤10000,1≤k≤5,1≤m≤30;
對於100%的數據,1≤N≤10000,1≤k≤10,1≤m≤100。

分析:

  • 因爲取反操作時連續一段的,將連續一段的區間問題轉化爲類似差分的東西。
    在連續一段都是1的開始位置設1,(結尾位置+1)的位置設1
  • 原問題<=>將所有點兩兩配對(費用是一個點通過各個size的操作走到另一個點的最少操作次數),且費用和最小。(費用的處理用bfs搞最短路)
  • 注意到k的範圍很小[1,10],所以這樣的點不超過20個,想到狀壓dp(s爲一個20位的2進制數,表示要處理的點的集合):
    dp[s]=min(dp[s], dp[s-(1<< i)-(1<< j)]+dist(i,j) ),i是新加入集合的點(即拿來配對的點),j是s中有的除i以外的點。
#include<cstdio>
#include<queue>
#include<algorithm>
#include<cassert>
using namespace std;
#define MAXN 10000
#define MAXK 10
#define MAXM 100
#define MAXST 1048576
#define INF 2000000000

int n,k,m,light[MAXN+10],siz[MAXM+10],id[MAXN+10],edge[MAXK*2+10][MAXK*2+10],cntp;
int f[MAXST+10],dist[MAXN+10],g[MAXST+10];
bool vis[MAXN+10];

void read()
{
    int x;
    scanf("%d%d%d",&n,&k,&m);
    for(int i=1;i<=k;i++){
        scanf("%d",&x);
        light[x]=1;
    }
    for(int i=n+1;i>=1;i--)
        light[i]^=light[i-1];
    for(int i=1;i<=n+1;i++)
        if(light[i])
            id[i]=++cntp;
    for(int i=1;i<=m;i++)
        scanf("%d",&siz[i]);
    sort(siz+1,siz+m+1);
    m=unique(siz+1,siz+m+1)-(siz+1);
}
void Bfs(int s)
{
    int u;
    for(int i=1;i<=n+1;i++)
        dist[i]=INF,vis[i]=false;
    queue<int>  que;
    que.push(s);
    vis[s]=true,dist[s]=0;
    while(!que.empty()){
        u=que.front(); que.pop();
        for(int i=1;i<=m;i++)
            if(u+siz[i]<=n+1&&!vis[u+siz[i]]){
                vis[u+siz[i]]=true;
                dist[u+siz[i]]=dist[u]+1;
                que.push(u+siz[i]);
            }
        for(int i=1;i<=m;i++)
            if(u-siz[i]>=1&&!vis[u-siz[i]]){
                vis[u-siz[i]]=true;
                dist[u-siz[i]]=dist[u]+1;
                que.push(u-siz[i]);
            }
    }
    for(int i=1;i<=n+1;i++){
        if(!light[i]) continue;
        if(vis[i])
            edge[id[s]][id[i]]=dist[i];
        else
            edge[id[s]][id[i]]=INF;
    }
}
void Getdist()
{
    for(int i=1;i<=n+1;i++)
        if(light[i])
            Bfs(i);
}
void DP()
{
    f[0]=0;
    for(int s=1;s<(1<<cntp);s++){ //這樣枚舉s,s從左往右的第一個是1的位置就是新增加的元素(即拿去匹配的元素)
        g[s]=g[s>>1]+(s&1);
        f[s]=INF;
        if(g[s]%2) continue;
        int newu=-1;
        for(int i=0;i<cntp;i++){
            if(s&(1<<i)){
                if(newu==-1) newu=i;
                else{
                    if(f[s-(1<<newu)-(1<<i)]!=INF)
                        f[s]=min(f[s],f[s-(1<<newu)-(1<<i)]+edge[newu+1][i+1]);
                }
            }
        }
    }
    printf("%d\n",f[(1<<cntp)-1]==INF?-1:f[(1<<cntp)-1]);
}
int main()
{
    read();
    Getdist();
    DP();
    return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章