Codeforces 1216F. Wi-Fi


傳送門

這個題一眼 $dp$

就是設 $f[i][0/1]$ 表示我們只考慮前 $i$ 個位置,並且保證覆蓋了前 $i$ 個位置,當前位置 選/不選 的最小代價

考慮轉移,設題目給出的字符串為 $s$

首先 $f[i][0]$ 必須從 $f[j][1]$ 轉移過來,其中 $ j+k>=i \text{ and } s[j]=1$

然后考慮 $f[i][1]$,如果 $s[i]=1$,那么我們可以從 $f[j][0]$ 和 $f[j][1]$ 轉移

並且只要保證 $i-k<=j+1$ 即可,就是保證讓 $i$ 覆蓋 $j+1$ 到 $i$ 這一段

然后如果 $s[i]=0$,那么我們首先可以從 $f[i-1][0/1]$ 轉移

並且也可以從 $f[j][1]$ 轉移,其中 $j$ 滿足 $j+k>=i-1 \text{ and } s[j]=1$

注意這里的邊界條件是 $j+k>=i-1$ 不是 $j+k>=i$,因為上一個站覆蓋到 $i-1$ 就行了,$i$ 位置自己覆蓋了

然后發現這個 $dp$ 轉移暴力復雜度是 $n^2$ 的,但是可以發現對於某個位置 $i$ 的轉移

對於 $f[i][0]$ ,我們要求一個區間內 $f[j][1]$ 的最小值,並且 $s[j]=1$

對於 $f[i][1]$ ,我們要求一個區間內 $f[j][0/1]$ 的最小值

所以維護兩顆線段樹,分別維護區間內 $f[j][1]$ 的最小值 和 區間內 $f[j][0/1]$ 的最小值

復雜度 $n \log n$

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<vector>
using namespace std;
typedef long long ll;
inline int read()
{
    int x=0,f=1; char ch=getchar();
    while(ch<'0'||ch>'9') { if(ch=='-') f=-1; ch=getchar(); }
    while(ch>='0'&&ch<='9') { x=(x<<1)+(x<<3)+(ch^48); ch=getchar(); }
    return x*f;
}
const int N=2e5+7;
const ll INF=1e18;
int n,K;
ll f[N][2];
char s[N];
struct SegTree {
    ll T[N<<2];
    SegTree () { memset(T,0x3f,sizeof(T)); }
    inline void pushup(int o) { T[o]=min(T[o<<1],T[o<<1|1]); }
    void ins(int o,int l,int r,int pos,ll v)
    {
        if(l==r) { T[o]=min(T[o],v); return; }
        int mid=l+r>>1;
        pos<=mid ? ins(o<<1,l,mid,pos,v) : ins(o<<1|1,mid+1,r,pos,v);
        pushup(o);
    }
    ll query(int o,int l,int r,int ql,int qr)
    {
        if(l>qr||r<ql) return INF;
        if(l>=ql&&r<=qr) return T[o];
        int mid=l+r>>1; return min(query(o<<1,l,mid,ql,qr),query(o<<1|1,mid+1,r,ql,qr));
    }
}T1,T2;
int main()
{
    n=read(),K=read(); scanf("%s",s+2);
    memset(f,0x3f,sizeof(f));
    f[1][1]=f[1][0]=0; T2.ins(1,1,n,1,0);
    for(int i=2;i<=n+1;i++)
    {
        f[i][0]=T1.query(1,1,n,max(1,i-K),i-1);
        if(s[i]=='1') f[i][1]=T2.query(1,1,n,max(1,i-K-1),i-1)+i-1;
        else f[i][1]=min( T1.query(1,1,n,max(1,i-K-1),i-1) , min(f[i-1][0],f[i-1][1]) )+i-1;
        T2.ins(1,1,n,i,f[i][1]); T2.ins(1,1,n,i,f[i][0]);
        if(s[i]=='1') T1.ins(1,1,n,i,f[i][1]);
    }
    printf("%lld\n",min(f[n+1][0],f[n+1][1]));
    return 0;
}
線段樹做法

 

這一題其實觀察題目的性質,選擇位置 $i$ 的代價為 $i$,也就是說代價隨着位置增加

發現到這里有單調性,考慮利用單調性 $dp$

直接設 $f[i]$ 表示把 $1$ 到 $i$ 覆蓋的最小代價

然后預處理出 $g[i]$ 表示從 $i$ 位置往右的第一個 $1$ 的位置

$g$ 的預處理顯然,考慮 $f[i]$ 怎么轉移

首先我們可以選擇 $i$ 位置,那么轉移顯然

然后考慮 $i$ 本身不選,選擇一個位置 $j$ ,使得 $j$ 能夠覆蓋 $i$

顯然我們考慮選擇的位置為 $g[i-k]$ (這里先不考慮 $i-k<1$ 的情況)

意思就是說,選擇位置 $i-k$ 往右的第一個 $1$ 位置(也就是最左邊能夠覆蓋 $i$ 的 $1$)

我們設這個位置為 $c$,考慮選擇位置 $c$ 的最小花費,因為 $f$ 單調不減,最小花費即為 $f[c-k-1]+c$

發現其實我們直接貪心地選擇位置 $c$ 一定比選擇 $c$ 后面的某個 $1$ 更優

因為考慮后面位置代價,首先選后面本身位置的代價就比選 $c$ 大,其次選后面的話,我們最優的 $f$ 也會變大

所以后面的一定不如位置 $c$,我們直接選擇位置 $c$ 轉移即可

代碼來自:LMOliver

(提醒一下,這個毒瘤的不知道是誰的大佬的代碼本機要把 $ifdef$ 去掉不然本機 $WA$,提交 $AC$,騙無知的我去 $hack$ $qwq$)

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
//這里往下本地運行要去掉
#if (!defined(__cplusplus) || __cplusplus > 201103)
/**
 * Use scanner in c++14, c++17 or c++20!
 */
template<class T>
struct Scanner{
    int value;
    Scanner(){
        value=0;
        int ch;
        while(isdigit(ch=getchar())){
            value=value*10+(ch^'0');
        }
    }
};
Scanner<int> qaq;
#else
#endif
//這里往上本地運行要去掉
const int N=200200;
char s[N];
int f[N];
int n,k;
LL dp[N];
int main(){
    scanf("%d%d",&n,&k);
    scanf("%s",s+1);
    f[n+1]=n+n+n;
    for(int i=n;i>=1;i--){
        f[i]=s[i]=='1'?i:f[i+1];
    }
    dp[0]=0;
    for(int i=1;i<=n;i++){
        dp[i]=dp[i-1]+i;
        int c=f[max(i-k,1)];
        if(c<=i+k){
            dp[i]=min(dp[i],dp[max(1,c-k)-1]+c);
        }
    }
    cout<<dp[n]<<endl;
    return 0;
}
單調性優化dp

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM