模擬106 題解

2022-03-16 19:19:06 字數 4371 閱讀 3184

顯然的區間dp。

斷環成鏈,預處理出每個連續區間集合的元素個數。

然後直接dp就完了。

想了一些簡單的貪心,然後都偽了。

所以考慮如何暴力$o(n^2)$來做這個題。

列舉最終用來跳最後一步的藥丸,顯然前面的藥丸可以按$a_i-b_i$排序。

然後考慮如何優化這個過程,問題在於如何判斷溺水的情況。

溺水的情況只出現在$a_i-b_i$的字首和小於等於$c_i$的字首和。

然而對於單個元素的刪除新增,很多元素的下標會出現移位,然後就很難搞。實際上並沒有很多,是我弱智了

乙個方法是線段樹分治,首先對所有的藥丸按$a_i-b_i$從大到小排序。

$solve(l,r)$表示用來跳最後一步的藥丸在$(l,r)$範圍內,已經考慮其它狀態。

這個做法的好處在於,當$(l,r)$確定,除掉這個區間內每個藥丸的排名也是確定的。

維護兩棵線段樹,下標為排名,

其中第一棵維護$a_i-b_i-c_i$字首和的最小值,第二棵維護$a_i-b_i$字首和的最大值。

遞迴$solve(mid+1,r)$之前,將$[l,mid]$之間的貢獻統計,暴力求出範圍內的字首和,之後區間加$l-1$字首和的值。

同理遞迴$solve(l,mid)$之前,將$[mid+1,r]$之間的貢獻統計,暴力求出範圍內的字首和,更新$[r+1,n]$之間元素的值。

當遞迴到$l=r$,可以現在第一棵線段樹上二分確定不會溺水的邊界,再在第二棵線段樹上二分確定最早可以跳出的時間,更新最終答案就好了。

雖然分治套線段樹,但是這個做法的複雜度是$o(nlogn)$的。

因為$solve$函式顯然只會呼叫$n$次,對於每次呼叫:

考場上打了$4k$**,一遍過對拍。

起初有兩處導致複雜度$o(nlog^2)$的地方,最終一步步改到了$o(nlogn)$,打起來確實很爽。

然而實際上我的做法並沒有什麼必要,因為鄰項的修改只會更改一位上的值,普通線段樹也可以實現。

1 #include2 #include3 #include4

#define ll long long

5#define cri const register

6#define lch p<<1

7#define rch p<<1|1

8using

namespace

std;

9const

int n=1e5+7;10

const

int inf=0x3f3f3f3f;11

struct

yaowanmed[n];

14int n,ans=inf;

15long

long

lim,pre[n],w[n];

16struct

nodes[n<<2];//

1表示最大值 不減pre的

19 inline void down(cri int

p)27

if(s[p].lzy[1

])34

}35 inline void up(cri int

p)39

void build(cri int p,cri int l,cri int

r)46

void toadd(cri int p,cri int fl,cri int l,cri int r,cri int l,cri int r,cri int

f)54

void modify(cri int p,cri int l,cri int r,cri ll val,cri int l,cri int

r)62 ll query(cri int p,cri int l,cri int r,cri int l,cri int

r)70

int ask(cri int p,cri ll add,cri int l,cri int

r)77

int find(cri int p,cri int l,cri int

r)84

void solve(cri int l,cri int

r)91 register int fr=n-1;92

if(s[1].val[0]<=0) fr=find(1,1,n-1)-1;93

if(fr&&query(1,1,fr,1,n-1)+med[l].a>=lim) ans=min(ans,ask(1,med[l].a,1,n-1)+1

);94

if(l!=1&&l!=n) modify(1,l,n-1,-val,1,n-1

);95

return;96

}97 cri int mid=l+r>>1

;98 w[mid]=0;99

for(register int i=mid+1;i<=r;++i) w[i]=w[i-1]+med[i].a-med[i].b;

100 toadd(1,mid+1,mid,r-1,1,n-1,1

);101

if(r!=n) modify(1,r,n-1,w[r],1,n-1

);102

solve(l,mid);

103 w[mid]=0

;104

for(register int i=mid+1;i<=r;++i) w[i]=w[i-1]+med[i].a-med[i].b;

105 toadd(1,mid+1,mid,r-1,1,n-1,-1

);106

if(r!=n) modify(1,r,n-1,-w[r],1,n-1

);107

108 w[l-1]=0

;109

for(register int i=l;i<=mid;++i) w[i]=w[i-1]+med[i].a-med[i].b;

110 toadd(1,l,l,mid,1,n-1,1

);111

if(l!=1

)115 solve(mid+1

,r);

116 w[l-1]=0

;117

for(register int i=l;i<=mid;++i) w[i]=w[i-1]+med[i].a-med[i].b;

118 toadd(1,l,l,mid,1,n-1,-1

);119

if(l!=1) modify(1,l,mid,-val,1,n-1

);120

}121 inline bool cmp(const yaowan &x,const yaowan &y)

124 inline int read(register int x=0,register char ch=getchar())

129int

main()

137for(int i=1;i<=n;++i) pre[i]=pre[i-1]+read();

138if(n==1) return puts("

-1"),0

;139 sort(med+1,med+n+1

,cmp);

140 build(1,1,n-1

);141 solve(1

,n);

142 printf("

%d\n

",ans==inf?-1

:ans);

143return0;

144 }

t2當存在一種方案,使得二者共同努力可以達到全部硬幣正面朝上,答案顯然不會低於2。

否則答案只與行數+列數的奇偶性有關。

所以首先判局面是否可以轉化到全部硬幣正面朝上:

對於每乙個硬幣,

如果它是正面,那麼行翻轉對應列翻轉,行不翻轉對應列不翻轉。

如果它是反面,那麼行翻轉對應列不翻轉,行不翻轉對應列翻轉。

發現這個東西可以用拓展域並查集簡單維護,

如果最終態存在矛盾,那麼無解,否則存在至少一組解。

之後考慮先手是否必勝。

對於每乙個集合,如果該集合對應著偶數個翻轉,其對立集合對應著偶數個翻轉,那麼顯然可以忽略這個集合。

其餘的兩個情況是:

該集合對應著奇數個翻轉,其對立集合對應著偶數個翻轉。

該集合對應著奇數個翻轉,其對立集合對應著奇數個翻轉。

所以只要考慮後兩種情況,設$dp(i,j)$表示前一種狀態有$i$個,後一種狀態有$j$個。

顯然$dp(0,0)=0$,對應先手必敗。

有轉移$dp(i,j)|=!dp(i,j-1)$,表示簡單選擇乙個兩面都是奇數的集合,將其轉化為沒用的偶數集合。

$dp(i,j)|=!dp(i-1,j)$,表示選擇乙個奇偶面集合的奇面,將其轉化為沒用的偶數集合。

$dp(i,j)|=!dp(i-1,j+1)$,表示選擇乙個奇偶面集合的偶面,那麼多乙個奇數集合。

NOIP2012模擬10 6 購買

description 小n 最近迷上了購物每天都讓小a 和小t 陪她逛街拿東西。最近商店出了這樣的乙個 活動 買東西送積分,就是買一件物品,送當前物品的積分ci 當前的倍率,初始倍率是1 input 第一行有乙個整數n表示要買的種類。接下來n行每行2個整數ki,ci表示數量和積分 接下來一行有乙個...

NOIP2012模擬10 6 購買

description 小n 最近迷上了購物每天都讓小a 和小t 陪她逛街拿東西。最近商店出了這樣的乙個 活動 買東西送積分,就是買一件物品,送當前物品的積分ci 當前的倍率,初始倍率是1 當倍率是i 的時候,如果你買的物品等於ti 個,那麼倍率將加1.最多積分的人可以得到超限 量版的圓神手辦。小n...

NOIP2012模擬10 6 填充棋盤

description 橫一劃豎一劃,橫一劃豎一劃 小r畫出了乙個n m的棋盤。由於noip快要到了,小r有了乙個奇妙的想法。在棋盤的每乙個小方格中填入n,o,i,p這4個字母中的乙個,若棋盤中每乙個2 2的小棋盤中都有n,o,i,p這4個字母,小r就認為這個棋盤是幸運棋盤。小r想知道一共有多少種不...