G
题意:
长度为 $n$ 的排列,允许交换任意的距离为 $x$ 的数字使数组升序,问,这样的 $x$ 有多少个?
思路:
观察发现,
1.相隔 $x$ 的所有数字经过任意次交换一定可以升序,可以按照对 $x$ 取模将数字分 $x$ 个块
2.想要将数组升序,每个数字肯定是从位置 $pos[i]$ 转移到 $i$,达成这个条件必须每个值都在同一个块内,也就是 $pos[i] \equiv i \mod{x}$
考虑如何将条件转化为对整个数组判断,发现最后取到的值满足 $G = gcd_{i=1}^{n}(|pos[i] - i|)$
$x$ 的取值就是 $G$ 的因子,则答案就是 $G$ 的因子个数
时间复杂度 $O(n+\sqrt{n})$
// 对于任意的 x,满足 pos[i]=i(mod x),
// 则 G = gcd(all(i)),
// 则答案的取值为 G 的因子个数
void solve(){cin >> n;vector<int> num(n+1), pos(n+1);For(i,1,n) cin >> num[i], pos[num[i]] = i;int G = 0;for(int i=1;i<=n;++i) G = gcd(G,abs(pos[i]-i));int ans = 0;for(int i=1;i<=G/i;++i){if(G%i==0){ans++;if(i*i!=G) ans++;}}if(G==0) ans = n;cout << ans << '\n';
}
I
题意:
$S$ 中匹配到所有 $T$ 的出现区间,在区间内标点表示区间染色,求所有区间染色的最少标记点数量
思路:
切到所有出现区间段,维护上一个选择的右端点,若当前左端点 $>$ 右端点,则 $ans++$,更新 $last$
H
题意:
$01$ 字符串切成两块的 $1$ 的数量减去 $0$ 的数量的乘积最大值
思路:
维护前缀和,枚举划分点,注意细节:答案可能为负数,初始化负无穷,不能选择 $n$ 位置作为划分点
void solve(){ cin >> n >> s, s = " " + s;vector<int> cnt0(n+1), cnt1(n+1);for(int i=1;i<=n;++i){cnt1[i] = cnt1[i-1], cnt0[i] = cnt0[i-1];if(s[i]=='0') cnt0[i]++;else cnt1[i]++;}ll ans = -LLONG_MAX; // 答案可能是负数,不能枚举非法点 nfor(int i=1;i<n;++i){int l = cnt1[i] - cnt0[i], r = cnt1[n] - cnt1[i] - (cnt0[n] - cnt0[i]);// cerr << l << ' ' << r << '\n';// r = max(r,0ll), l = max(l,0ll);ans = max(ans, 1ll*l*r);}cout << ans << '\n';
}
F
思路:
经典枚举分界点,处理前后缀,答案取min就好了
考虑前后缀如何维护,先找到该位置出现的第一个区间起点,然后合并区间,这里可以用set维护信息来做
考验码力的时候到了
struct Node{int l,r,cnt;bool operator<(const Node o) const{return l<o.l;}
};void Solve(){cin >> n >> k;vector<int> num(n+1);For(i,1,n) cin >> num[i];set<int> L,R;vector<int> pre(n+1),suf(n+2);{set<Node> S;for(int i=1;i<=n;++i){int L = num[i], wait = 0;auto it = S.lower_bound({L,0,0});//看左邻居是否覆盖到 t[i]if(it != S.begin()){auto last = prev(it);if(last->r > L) L = last->r;}wait += (L-num[i]);int R = L+k, ncnt = 1; // 向右合并:把右侧与 [L,R) 交/相邻的段整体右移it = S.lower_bound({L,0,0});while(it != S.end() && it->l <= R){int d = R - it->l;R = it->r + d, ncnt += it->cnt;wait += 1ll*d*(it->cnt);it = S.erase(it);}// 与左侧相邻段合并(如果恰好相接)it = S.lower_bound({L,0,0});if(it!=S.begin()){auto last = prev(it);if(last->r==L){L = last->l, ncnt += last->cnt;S.erase(last);}}S.insert({L,R,ncnt});pre[i] = pre[i-1] + wait;}}// 后缀合并区间set<Node> S;for(int i=n;i>=1;--i){int L = num[i], wait = 0;auto it = S.lower_bound({L,0,0});//看左邻居是否覆盖到 t[i]if(it != S.begin()){auto last = prev(it);if(last->r > L) L = last->r;}wait += (L-num[i]);int R = L+k, ncnt = 1; // 向右合并:把右侧与 [L,R) 交/相邻的段整体右移it = S.lower_bound({L,0,0});while(it != S.end() && it->l <= R){int d = R - it->l;R = it->r + d, ncnt += it->cnt;wait += 1ll*d*(it->cnt);it = S.erase(it);}// 与左侧相邻段合并(如果恰好相接)it = S.lower_bound({L,0,0});if(it!=S.begin()){auto last = prev(it);if(last->r==L){L = last->l, ncnt += last->cnt;S.erase(last);}}S.insert({L,R,ncnt});suf[i] = suf[i+1] + wait;}// For(i,1,n) cerr << pre[i] << " \n"[i==n];// For(i,1,n) cerr << suf[i] << " \n"[i==n];ll ans = LLONG_MAX;for(int i=1;i<=n;++i) ans = min(ans, pre[i] + suf[i+1]);cout << ans << '\n';
}
K
经典数数题,就是题解那个意思,推到最后公式是
$$
ans = \sum_{i=1}^{min(k,n-k+1)} \binom{n-k+1}{i} \binom{k-1}{i-1} m^i (m-1)^{k-i}
$$
void Solve(){cin >> n >> m >> k;if(m == 1){cout << binom(n-k+1,k) <<'\n';return;}ll ans = 0, pwm = m, pwm1 = qmi(m-1,k-1,Mod), invm = inv(m-1);for(int i=1;i<=min(k,n-k+1);++i){ll tmp = 1ll*binom(k-1,i-1)%Mod*binom(n-k+1,i)%Mod;ans = (ans + tmp*pwm%Mod*pwm1%Mod) % Mod;pwm = pwm*m%Mod, pwm1 = pwm1*invm%Mod;}cout << ans << '\n';
}
D
用线段树的方式做 01 背包即可
代码是抄的
#include<bits/stdc++.h>
using namespace std;
#define x first
#define y second
//#define int long long
#define lowbit(x) (x&(-x))
#define pii pair<int,int>
#define debug(x) {cerr << #x << " = " << x << "\n";}
const int N = 1e3 + 1,M = 1e4 + 1;
vector<int>wi(N),vi(N);
const int D = __lg(N) + 1;
int dpl[D][N][M],dpr[D][N][M];
void build(int l,int r,int dp) {if(l == r) return ;int mid = l + r >> 1;for(int i = wi[mid]; i < M; i++) {dpr[dp][mid][i] = vi[mid];}for(int i = wi[mid + 1]; i < M; i++) {dpl[dp][mid + 1][i] = vi[mid + 1];}for(int i = mid - 1; i >= l; i--) {for(int j = 0; j < M; j++) {dpr[dp][i][j] = dpr[dp][i + 1][j];if(j >= wi[i]) {dpr[dp][i][j] = max(dpr[dp][i + 1][j - wi[i]] + vi[i],dpr[dp][i][j]);}}}for(int i = mid + 2; i <= r; i++) {for(int j = 0; j < M; j++) {dpl[dp][i][j] = dpl[dp][i - 1][j];if(j >= wi[i]) {dpl[dp][i][j] = max(dpl[dp][i - 1][j - wi[i]] + vi[i],dpl[dp][i][j]);}}}build(l,mid,dp + 1);build(mid + 1,r,dp + 1);
}
int query(int l,int r,int ll,int rr,int x,int p) {int mid = l + r >> 1;int ans = 0;if(mid >= ll && mid <= rr) {for(int i = 0; i <= p; i++) {ans = max(ans,dpr[x][ll][i] + dpl[x][rr][p - i]);}return ans;}if(mid > rr) ans = query(l,mid,ll,rr,x + 1,p);else ans = query(mid + 1,r,ll,rr,x + 1,p);return ans;
}
void miss() {int n;cin >> n; for(int i = 1; i <= n; i++) {cin >> wi[i];}for(int i = 1; i <= n; i++) {cin >> vi[i];}build(1,n,0);int q;cin >> q;int last = 0;while(q--){int xl,xr,xw;cin >> xl >> xr >> xw;xl = (xl+last)%n + 1;xr = (xr+last)%n + 1;xw = (xw+last)%10000 + 1;if(xl > xr)swap(xl,xr);if(xl == xr && xw >= wi[xl]) last = vi[xl];else last = query(1,n,xl,xr,0,xw);cout << last << endl;}
}
signed main() {ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);int T = 1;//cin >> T;while(T--) miss();
}