A
判斷一下前面能否空出來就就行,也就是 \(l \geq r-l+1\)。
#include <bits/stdc++.h>
#define fi first
#define se second
#define db double
#define U unsigned
#define P std::pair<int,int>
#define LL long long
#define pb push_back
#define MP std::make_pair
#define all(x) x.begin(),x.end()
#define CLR(i,a) memset(i,a,sizeof(i))
#define FOR(i,a,b) for(int i = a;i <= b;++i)
#define ROF(i,a,b) for(int i = a;i >= b;--i)
#define DEBUG(x) std::cerr << #x << '=' << x << std::endl
int main(){
int T;scanf("%d",&T);
while(T--){
int l,r;scanf("%d%d",&l,&r);
puts(l >= r-l+1 ? "YES" : "NO");
}
return 0;
}
B
相當於不能有子串 11
和 00
。
那麼如果有 11
肯定在後面有一個地方有 00
(反證),所以每次操作最多可以消去兩個這樣的連續的東西,求出有多少個相鄰相同的位置 \(x\),答案就是 \(\lceil \frac{x}{2} \rceil\)。
#include <bits/stdc++.h>
#define fi first
#define se second
#define db double
#define U unsigned
#define P std::pair<int,int>
#define LL long long
#define pb push_back
#define MP std::make_pair
#define all(x) x.begin(),x.end()
#define CLR(i,a) memset(i,a,sizeof(i))
#define FOR(i,a,b) for(int i = a;i <= b;++i)
#define ROF(i,a,b) for(int i = a;i >= b;--i)
#define DEBUG(x) std::cerr << #x << '=' << x << std::endl
const int MAXN = 1e5 + 5;
char str[MAXN];
int main(){
int T;scanf("%d",&T);
while(T--){
int n;scanf("%d",&n);
scanf("%s",str+1);
int ans = 0;
FOR(i,2,n) if(str[i] == str[i-1]) ans++;
ans = (ans+1)>>1;
printf("%d\n",ans);
}
return 0;
}
C
首先可以發現一個東西:將匹配方式連邊,一定存在一種分配方式使得最優解沒有包含關係的線段,因爲包含關係可以通過交換變成相交,並且代價不變。
排序後可以設 \(f_{i,j}\) 表示考慮了前 \(i\) 個數,最小的 \(j\) 滿足 \(j\) 開始後面一段數字都沒有被用過的代價,轉移直接枚舉下一個填啥就行了。
#include <bits/stdc++.h>
#define fi first
#define se second
#define db double
#define U unsigned
#define P std::pair<int,int>
#define LL long long
#define pb push_back
#define MP std::make_pair
#define all(x) x.begin(),x.end()
#define CLR(i,a) memset(i,a,sizeof(i))
#define FOR(i,a,b) for(int i = a;i <= b;++i)
#define ROF(i,a,b) for(int i = a;i >= b;--i)
#define DEBUG(x) std::cerr << #x << '=' << x << std::endl
const int MAXN = 600+5;
int n,a[MAXN];
bool vis[MAXN];
int f[2][MAXN],now;
int main(){
int T;scanf("%d",&T);
while(T--){
scanf("%d",&n);
FOR(i,1,n) scanf("%d",a+i);
std::sort(a+1,a+n+1);
CLR(f,0x3f);
f[now = 0][0] = 0;
FOR(i,1,n){
CLR(f[now^1],0x3f);
FOR(j,0,3*n){
if(f[now][j] == 0x3f3f3f3f) continue;
FOR(k,j+1,3*n){
f[now^1][k] = std::min(f[now^1][k],f[now][j]+std::abs(a[i]-k));
}
}
now ^= 1;
}
int ans = 1e9;
FOR(i,1,3*n) ans = std::min(ans,f[now][i]);
printf("%d\n",ans);
}
return 0;
}
D
感性理解一下,我們每次取出深度最小的點,接上連續的一段遞增序列作爲它的後繼即可。
#include <bits/stdc++.h>
#define fi first
#define se second
#define db double
#define U unsigned
#define P std::pair<int,int>
#define LL long long
#define pb push_back
#define MP std::make_pair
#define all(x) x.begin(),x.end()
#define CLR(i,a) memset(i,a,sizeof(i))
#define FOR(i,a,b) for(int i = a;i <= b;++i)
#define ROF(i,a,b) for(int i = a;i >= b;--i)
#define DEBUG(x) std::cerr << #x << '=' << x << std::endl
const int MAXN = 2e5 + 5;
int n,a[MAXN];
int main(){
int T;scanf("%d",&T);
while(T--){
scanf("%d",&n);FOR(i,1,n) scanf("%d",a+i);
std::priority_queue<P,std::vector<P>,std::greater<P> > q;
q.push(MP(0,1));int p = 2;
int ans = 0;
while(!q.empty()){
int dep = q.top().fi;q.pop();ans = std::max(ans,dep);
int las = -1;std::vector<int> v;
while(p <= n && las <= a[p]) las = a[p++],v.pb(las);
for(auto x:v) q.push(MP(dep+1,x));
}
printf("%d\n",ans);
}
return 0;
}
E
如果沒有不能選的限制就是經典 dp:我們觀察兩個位置 \(i<j\) 能同時不改變的條件是 \(a_j-a_i-1 \geq j-i-1\),也就是 \(a_i-i \leq a_j-j\),最多能不修改的數也就是設 \(b_i=a_i-i\),\(b_i\) 的非嚴格最長上升子序列長度。
這個題限定某些數不能改變,先判完無解後相當於變成了強制選取某些位置的最長上升子序列,對於每段分別做即可。
注意這裏 infty
開大點。。要不然判無解會判錯。。。真正考試對拍的時候還是多試試 corner case 吧。。
#include <bits/stdc++.h>
#define fi first
#define se second
#define db double
#define U unsigned
#define P std::pair<int,int>
#define LL long long
#define pb push_back
#define MP std::make_pair
#define all(x) x.begin(),x.end()
#define CLR(i,a) memset(i,a,sizeof(i))
#define FOR(i,a,b) for(int i = a;i <= b;++i)
#define ROF(i,a,b) for(int i = a;i >= b;--i)
#define DEBUG(x) std::cerr << #x << '=' << x << std::endl
const int MAXN = 5e5 + 5;
int n,k;
LL a[MAXN];
std::vector<int> S;
std::vector<LL> SS;
struct BIT{
#define lowbit(x) ((x)&(-(x)))
int tree[MAXN],ts[MAXN],now;
inline void reset(){
++now;
}
inline void add(int pos,int x){
while(pos < MAXN){
if(ts[pos] != now) tree[pos] = 0,ts[pos] = now;
tree[pos] = std::max(tree[pos],x);
pos += lowbit(pos);
}
}
inline int query(int pos){
int res = 0;if(!pos) return 0;
while(pos){
if(ts[pos] != now) tree[pos] = 0,ts[pos] = now;
res = std::max(res,tree[pos]);
pos -= lowbit(pos);
}
return res;
}
}bit;
int b[MAXN];
int main(){
scanf("%d%d",&n,&k);
FOR(i,1,n) scanf("%d",a+i);
a[0] = -1e18;a[n+1] = 1e18;
FOR(i,0,n+1) SS.pb(a[i]-i);
std::sort(all(SS));SS.erase(std::unique(all(SS)),SS.end());
FOR(i,0,n+1) b[i] = std::lower_bound(all(SS),a[i]-i)-SS.begin()+1;
S.pb(0);
FOR(i,1,k){
int x;scanf("%d",&x);
S.pb(x);
}
S.pb(n+1);
FOR(i,1,(int)S.size()-1){
if(a[S[i]]-a[S[i-1]] < S[i]-S[i-1]){
puts("-1");
return 0;
}
}
int ans = 0;
FOR(i,1,(int)S.size()-1){
auto work = [&](int l,int r){
bit.reset();
int res = 0;
FOR(i,l,r){
if(b[i] < b[l]) continue;
res = bit.query(b[i])+1;
bit.add(b[i],res);
}
return r-l+1-res;
};
ans += work(S[i-1],S[i]);
}
printf("%d\n",ans);
return 0;
}
F
居然讓我想了一會。。不過也是簡單題
直接做我們可能需要記錄哪些數選了,狀態會爆炸。我們考慮不斷往後面加數,如果這個序列的最大值確定了,那麼這個序列不改變最大值的前提下的最大長度就確定了,如果知道了目前長度,我們就能知道不改變最大值前提下這個位置的數有多少種可能。
於是設 \(f_{i,j}\) 表示放了 \(i\) 個數,最大值是 \(j\) 的方案數,預處理 \(las_i\) 表示最大值是 \(i\) 的時候序列的最長長度,\(nxt_i\) 表示最大值是 \(i\) 要改變最大值可以放的最小的數,\(cnt_i\) 表示值爲 \(i\) 的數的個數,轉移:
- 不改變最大值:\(f_{i,j}\times (las_j-i+1) \to f_{i+1,j}\)
- 改變最大值:\(f_{i,j}\times cnt_k \to f_{i+1,k}(k \geq nxt_j)\)
用一些前綴和技巧可以優化到 \(O(n^2)\)。
看了一個比較 nb 的做法,大概是我們 dp 的時候只在放滿的時候轉移(也就是最大值爲 \(j\) 的時候我們只考慮狀態 \(f_{las_j,j}\)),這個就是設 \(f_i\) 表示最大值爲 \(i\) 的方案數,這樣長度就是 \(las_i+1\),轉移的時候枚舉上一次的最大值,乘上一個係數選進去就行了。
然後前綴和優化一下就 \(O(n)\) 了。還是要考慮大多數 dp 狀態都是無用的,只對真正有用的決策即可。
#include <bits/stdc++.h>
#define fi first
#define se second
#define db double
#define U unsigned
#define P std::pair<int,int>
#define LL long long
#define pb push_back
#define MP std::make_pair
#define all(x) x.begin(),x.end()
#define CLR(i,a) memset(i,a,sizeof(i))
#define FOR(i,a,b) for(int i = a;i <= b;++i)
#define ROF(i,a,b) for(int i = a;i >= b;--i)
#define DEBUG(x) std::cerr << #x << '=' << x << std::endl
const int MAXN = 5000+5;
const int ha = 998244353;
int a[MAXN],n;
int f[2][MAXN],cf[MAXN];
// 選了 i 個數 最大值是 j
std::vector<int> S;
int las[MAXN];// 選了 i 之後 還有幾個數可以放
int nxt[MAXN];// 選了 i 之後 下一個必須選啥
int cnt[MAXN];
inline void add(int &x,int y){
x += y-ha;x += x>>31&ha;
}
int main(){
scanf("%d",&n);
FOR(i,1,n) scanf("%d",a+i);
std::sort(a+1,a+n+1);
FOR(i,1,n) S.pb(a[i]);S.erase(std::unique(all(S)),S.end());
FOR(i,1,n) cnt[std::lower_bound(all(S),a[i])-S.begin()+1]++;
int m = S.size();
FOR(i,1,m){
las[i] = las[i-1];
while(las[i]+1 <= n && a[las[i]+1]*2 <= S[i-1]) las[i]++;
}
nxt[0] = 1;
FOR(i,1,m){
nxt[i] = -1;
FOR(j,i+1,m){
if(S[i-1]*2 <= S[j-1]) {nxt[i] = j;break;}
}
}
int now = 0;f[now][0] = 1;
FOR(i,1,n){
CLR(f[now^1],0);CLR(cf,0);
FOR(j,0,m){
if(!f[now][j]) continue;
int t = las[j]-i+1+1;
if(j == 0) t = 0;
if(t > 0) add(f[now^1][j],1ll*f[now][j]*t%ha);
if(nxt[j] != -1) add(cf[nxt[j]],f[now][j]);
}
FOR(j,1,m) add(cf[j],cf[j-1]),add(f[now^1][j],1ll*cf[j]*cnt[j]%ha);
now ^= 1;
// FOR(i,0,m) printf("%d ",f[now][i]);puts("");
}
printf("%d\n",f[now][m]);
return 0;
}
G
建 AC 自動機,我們枚舉詢問串的每一個後綴,走到對應的節點上,這個串的子串就是 fail 樹的祖先,所以相當於要支持單點修改,查詢到根的一條鏈上的最大值,樹剖即可。\(O(n \log^2 n)\)。
題解做法一:我們發現對於一個點,它在 fail 樹上到根的路徑上終止節點最多有 \(O(\sqrt n)\) 個(因爲 \(\sum_{i=1}^n i = O(n^2)\)),所以記錄一下每個點上面距離最近的終止節點,暴力跳就好了。\(O(q\sqrt n)\)。
一個比較 nb 的離線單 log 做法:先把詢問和修改掛在樹上,我們經過一個點 apply 它的所有操作,現在只有了操作時間的限制,我們用一個線段樹,第 \(i\) 個位置維護操作時間爲 \(i\) 的答案,不難發現要支持區間對一個數取 \(\max\),撤銷,單點求值。因爲只需要單點求值,搞個標記永久化就好了,這樣就只會改變 \(O(\log n)\) 個節點,可以存下來了。這種線段樹+撤銷都可以考慮下標記永久化...
#include <bits/stdc++.h>
#define fi first
#define se second
#define db double
#define U unsigned
#define P std::pair<int,int>
#define LL long long
#define pb push_back
#define MP std::make_pair
#define all(x) x.begin(),x.end()
#define CLR(i,a) memset(i,a,sizeof(i))
#define FOR(i,a,b) for(int i = a;i <= b;++i)
#define ROF(i,a,b) for(int i = a;i >= b;--i)
#define DEBUG(x) std::cerr << #x << '=' << x << std::endl
const int MAXN = 3e5 + 5;
int ch[MAXN][26],fail[MAXN],tot = 1,rt = 1;
int ps[MAXN];
inline void insert(char str[],int id){
int len = strlen(str+1),v = rt;
FOR(i,1,len){
int o = str[i]-'a';
if(!ch[v][o]) ch[v][o] = ++tot;
v = ch[v][o];
}
ps[id] = v;
}
std::vector<int> G[MAXN];
inline void build(){
std::queue<int> q;
FOR(i,0,25){
if(ch[rt][i]) q.push(ch[rt][i]),fail[ch[rt][i]] = rt;
else ch[rt][i] = rt;
}
while(!q.empty()){
int v = q.front();q.pop();
FOR(i,0,25){
if(ch[v][i]) q.push(ch[v][i]),fail[ch[v][i]] = ch[fail[v]][i];
else ch[v][i] = ch[fail[v]][i];
}
}
FOR(i,2,tot) G[fail[i]].pb(i);
}
int mx[MAXN<<2];
#define lc ((x)<<1)
#define rc ((x)<<1|1)
inline void update(int x,int l,int r,int p,int d){
if(l == r){
mx[x] = d;
return;
}
int mid = (l + r) >> 1;
if(p <= mid) update(lc,l,mid,p,d);
else update(rc,mid+1,r,p,d);
mx[x] = std::max(mx[lc],mx[rc]);
}
inline int query(int x,int l,int r,int L,int R){
if(l == L && r == R) return mx[x];
int mid = (l + r) >> 1;
if(R <= mid) return query(lc,l,mid,L,R);
if(L > mid) return query(rc,mid+1,r,L,R);
return std::max(query(lc,l,mid,L,mid),query(rc,mid+1,r,mid+1,R));
}
int dfn[MAXN],sz[MAXN],tp[MAXN],fa[MAXN],son[MAXN];
inline void dfs1(int v){
sz[v] = 1;
for(auto x:G[v]){
fa[x] = v;
dfs1(x);sz[v] += sz[x];
if(sz[son[v]] < sz[x]) son[v] = x;
}
}
inline void dfs2(int v,int tp=1){
static int ts = 0;dfn[v] = ++ts;::tp[v] = tp;
if(son[v]) dfs2(son[v],tp);
for(auto x:G[v]){
if(x == son[v]) continue;
dfs2(x,x);
}
}
inline int query(int x){
int res = -1;
while(tp[x] != 1){
res = std::max(res,query(1,1,tot,dfn[tp[x]],dfn[x]));
x = fa[tp[x]];
}
res = std::max(res,query(1,1,tot,dfn[1],dfn[x]));
return res;
}
int n,m;
std::multiset<int> S[MAXN];
int val[MAXN];
char str[MAXN];
int main(){
scanf("%d%d",&n,&m);
CLR(mx,-1);
FOR(i,1,n){
scanf("%s",str+1);
insert(str,i);
S[ps[i]].insert(0);
}
build();
dfs1(1);dfs2(1);
FOR(i,1,n) update(1,1,tot,dfn[ps[i]],*S[ps[i]].rbegin());
while(m--){
int opt;scanf("%d",&opt);
if(opt == 1){
int p,x;scanf("%d%d",&p,&x);
S[ps[p]].erase(S[ps[p]].find(val[p]));
val[p] = x;
S[ps[p]].insert(val[p]);
update(1,1,tot,dfn[ps[p]],*S[ps[p]].rbegin());
}
else{
scanf("%s",str+1);int len = strlen(str+1);
int v = 1,res = -1;
FOR(i,1,len){
v = ch[v][str[i]-'a'];
res = std::max(res,query(v));
}
printf("%d\n",res);
}
}
return 0;
}