前言:
已经过了: A,B,C,D,E,F
这一场比较牛,第三题就已经2000分了。
A.
std出锅了。主要是3 1 2 这样的会使得共点存在.
B. 贪心: 1800
题目大意:
给你一个只含小写字母的字符串。问你重新排列这字符串使得相邻字符在字符表中都不相邻.(a,z不相邻)。
思路:
方法一:随机化
其实我们发现相同的字母放在一起是最好的。如果分开放,会导致更多的限制.
所以先去重,接着考虑如何放。当时没想太清楚。只是觉得合法的情况占绝大多数。不合法的情况反而少。所以random_suffle 500次check即可.500次以内没出正确结果就没有合法情况.
使用方法:
srand(unsigned(time(NULL)));
...
random_shuffle(a.begin(), a.end());
方法二:贪心
不让我们相差为1.那么就构造相差为2呗。那么就将奇数位放在一起,偶数位的字母放在一起。然后拼在一起即可。
其实这个问题的非法情况很少。当不同字符个数 d i f ≥ 4 dif \geq 4 dif≥4的时候,就一定存在解了。所以就连dfs都能做.
d i f = 1 dif=1 dif=1 不存在解。

d i f = 2 ∣ ∣ 3 dif=2||3 dif=2∣∣3 ,当它们字母序相邻时不存在解。反之有解.
C.
题目大意:
在一维坐标轴上给你若干个点。问你匹配最多的点对。使得两点之间的距离大于等于z.
n ≤ 1 e 5 n \leq 1e5 n≤1e5
题目思路:贪心+二分
对于任意一个已经匹配的点对 ( l , r ) (l,r) (l,r).如果有未匹配的点 x < l x < l x<l.那么可以换成 ( x , r ) (x,r) (x,r)而不会使得答案变差。
对于任意一个已经匹配的点对 ( l , r ) (l,r) (l,r).如果有未匹配的点 x > r x > r x>r.那么可以换成 ( l , x ) (l,x) (l,x)而不会使得答案变差。
所以对于一个确定个数 k k k,如果能找到 k k k个点对。那么这 k k k个点对一定可以被转化成最左边 k k k个点与最右边 k k k个点相匹配。然后为了最大化两点对之间的最小差距,我们一定是左边最小匹配右边最小,依次这么进行。
然后 k k k越大,越难成立。符合单调性。二分答案即可。
AC代码:
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define pii pair<int,int>
#define pb push_back
#define mp make_pair
#define vi vector<int>
#define vl vector<ll>
const int maxn = 1e6 + 5;
const int mod = 1e9 + 7;
int a[maxn] , b[maxn];
int main()
{
ios::sync_with_stdio(false);
int n , z; cin >> n >> z;
for (int i = 1 ; i <= n ; i++) cin >> a[i];
sort(a + 1 , a + 1 + n);
int l = 1 , r = n / 2;
while (l <= r){
int mid = l + r >> 1 , cnt = 0;
for (int i = n - mid + 1 ; i <= n ; i++)
b[++cnt] = a[i];
bool ok = true;
for (int i = 1 ; i <= mid ; i++)
if (b[i] - a[i] < z) ok = false;
if (ok) l = mid + 1;
else r = mid - 1;
}
cout << r << endl;
return 0;
}
D. 0-1-Tree: 树形dp/巧妙并查集
题目大意:
给你一张只有0/1边权的树。问你有多少条有序路径满足它先访问一系列0,后面全访问1.
方法一:树形dp
这个方法很显然。过程类似树形背包。对一个点,每次新增一颗子树,就计算一遍贡献。边dp边求答案.
d p ( i , 0 / 1 / 2 / 3 ) dp(i,0/1/2/3) dp(i,0/1/2/3)代表从 i i i的子树的点到 i i i点路径为 0..0 / 11..1 / 00..11 / 11..00 0..0/11..1/00..11/11..00 0..0/11..1/00..11/11..00形式的路径个数.然后对于 i i i点的不同子树,贡献拼接在一起即可。类似聪明可可那题
具体过程:三步走
1.先通过 d p ( v ) dp(v) dp(v)求出到 u u u点的路径个数,用 t m p tmp tmp数组存.
2.因为是边权,我们对于 u u u点,拉出一个连接着 u u u的,边权等于 ( u , v ) (u,v) (u,v)的虚点 x x x.然后统计点对答案.然后删除虚点.
3.将 t m p tmp tmp数组归到 d p ( u ) dp(u) dp(u)中。
AC代码:
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define pii pair<int,int>
#define pb push_back
#define mp make_pair
#define vi vector<int>
#define vll vector<ll>
const int maxn = 2e5 + 5;
const int mod = 1e9 + 7;
int a[maxn];
vector<pii> e[maxn];
ll dp[maxn][4] , tmp[5];
ll ans = 0;
void dfs (int u , int fa , int c)
{
bool ye = true;
for (auto g : e[u]){
int v = g.first , w = g.second;
if (v == fa) continue;
ye = false;
dfs(v , u , w);
// tmp 代表从子树v到u这个点有多少个路径
memset(tmp , 0 , sizeof tmp);
if (w == 0){
tmp[0] = dp[v][0] + 1;
tmp[1] = 0;
tmp[2] = dp[v][2] + dp[v][1];
tmp[3] = 0;
}else {
tmp[0] = 0;
tmp[1] = dp[v][1] + 1;
tmp[2] = 0;
tmp[3] = dp[v][3] + dp[v][0];
}
// 统计答案
dp[u][w]++;
ans += dp[u][0] * (2 * tmp[0] + tmp[1] + tmp[2]);
ans += dp[u][1] * (2 * tmp[1] + tmp[0] + tmp[3]);
ans += dp[u][2] * tmp[0];
ans += dp[u][3] * tmp[1];
dp[u][w]--;
for (int i = 0 ; i < 4 ; i++){
dp[u][i] += tmp[i];
}
}
return ;
}
int main()
{
ios::sync_with_stdio(false);
int n; cin >> n;
for (int i = 1 ; i < n ; i++){
int x , y , z; cin >> x >> y >> z;
e[x].pb(mp(y,z));
e[y].pb(mp(x,z));
}
dfs(1 , 0 , 0);
cout << ans << endl;
return 0;
}
方法二:巧妙并查集
由于左半边全是 0 0 0,右半边全是 1 1 1.那这条路径上一定会有一个点是 0 / 1 0/1 0/1分割点。所以我们可以枚举这个分割点。这就是这个方法的核心思想。
有了这个思想这题就很简单了,求出只走0的连通块,再求出只走1的连通块。
对于点 x x x,答案就是 s z [ 0 ] ∗ s z [ 1 ] − 1 sz[0] * sz[1] -1 sz[0]∗sz[1]−1.减一是去除掉 x − > x x->x x−>x这样的路径。
AC代码:
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define pii pair<int,int>
#define pb push_back
#define mp make_pair
#define vi vector<int>
#define vll vector<ll>
const int maxn = 2e5 + 5;
const int mod = 1e9 + 7;
struct Node{
int x , y;};
vector<Node> e[2];
int f[2][maxn];
ll sz[2][maxn];
int getf (int x , int f[]) {
return x == f[x] ? x : f[x] = getf(f[x] , f);}
bool mer (int a , int b , int f[] , ll sz[])
{
int fa = getf(a , f) , fb = getf(b , f);
if (fa == fb) return false;
f[fb] = fa;
sz[fa] += sz[fb];
return true;
}
int main()
{
ios::sync_with_stdio(false);
int n; cin >> n;
for (int i = 1 ; i < n ; i++){
int x , y , z; cin >> x >> y >> z;
e[z].push_back({
x , y});
}
ll ans = 0;
for (int t = 0 ; t <= 1 ; t++){
for (int i = 1 ; i <= n ; i++) f[t][i] = i , sz[t][i] = 1;
for (auto g : e[t]) mer(g.x , g.y , f[t] , sz[t]);
}
for (int i = 1 ; i <= n ; i++) ans += sz[0][getf(i , f[0])] * sz[1][getf(i , f[1])] - 1;
cout << ans << endl;
return 0;
}
// 后面两题反而还容易了。。
E. Special Segments of Permutation
题目大意:
给你一个长度为 n n n的排列,问你有多少个子数组满足: a l + a r = m a x { a l , . . , a r } a_l+a_r=max\{a_l,..,a_r\} al+ar=max{
al,..,ar}.
n ≤ 1 e 5 n \leq 1e5 n≤1e5
题目思路:
考虑枚举。枚举左式的话就是枚举左端点,找右端点。不太好做。发现枚举右边比较好做。对于整体最大值,所有经过它的子数组右式都等于它.又由于是排列,我们只需要枚举一边,找另一边是否存在 m x − a mx-a mx−a即可。找完之后分成两个子区间分治求解.因为子数组分为,完全在 m x mx mx左边,完全在 m x mx mx右边。跨过 m x mx mx的。 解决完第三种后递归求解两边即可。注意我们要枚举短的一边,这样复杂度最大为 O ( n l o g n ) O(nlogn) O(nlogn).
为了找到区间最大值,我们需要再求个st表.
AC代码:
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define pii pair<int,int>
#define pb push_back
#define mp make_pair
#define vi vector<int>
#define vll vector<ll>
const int maxn = 2e5 + 5;
const int mod = 1e9 + 7;
int a[maxn] , pos[maxn] , dp[maxn][20] , lg[maxn] , ans , n;
void st ()
{
lg[0] = -1;
for (int i = 1 ; i <= n ; i++){
dp[i][0] = a[i];
lg[i] = lg[i >> 1] + 1;
}
for (int j = 1 ; j <= 19 ; j++)
for (int i = 1 ; i + (1 << j) - 1 <= n ; i++)
dp[i][j] = max (dp[i][j - 1] , dp[i + (1 << (j - 1))][j - 1]);
}
int ask (int l , int r)
{
int len = r - l + 1;
return max(dp[l][lg[len]] , dp[r - (1 << lg[len]) + 1][lg[len]]);
}
void dfs (int l , int r)
{
if (r - l + 1 < 3) return ;
int mx = ask(l , r) , id = pos[mx];
int len1 = id - l , len2 = r - id;
if (len1 < len2){
for (int i = l ; i < id ; i++){
int v = pos[ mx - a[i] ];
if (id + 1 <= v && v <= r) ans++;
}
}else {
for (int i = id + 1 ; i <= r ; i++){
int v = pos[ mx - a[i] ];
if (l <= v && v < id) ans++;
}
}
dfs(l , id - 1);
dfs(id + 1 , r);
return ;
}
int main()
{
ios::sync_with_stdio(false);
cin >> n;
for (int i = 1 ; i <= n ; i++){
cin >> a[i];
pos[a[i]] = i;
}
st();
dfs(1 , n);
cout << ans << endl;
return 0;
}
F. Card Bag
题目大意:
n n n个数.你随机从里面拿数。当你拿出来的序列是严格递增的,并且最后一次拿的数等于上一次拿的数时,你赢了,如果还是严格递增,继续。如果不符合递增,输了。拿完也输了。问你这个过程赢的概率。
1 ≤ a i ≤ n ≤ 5000 1 \leq a_i \leq n \leq 5000 1≤ai≤n≤5000
题目思路:
性质:
1.每次拿数事件独立
2.一个数 k k k出现次数至少2次才可能以它为结尾作为赢的局面。
一个显然的做法:对原数组排序去重。枚举以 x x x为结尾作为赢的局面。那么接下来再枚举这个序列最终的长度。
那么问题转化为:求从大于等于 x x x的数中选择出 k k k种不同的数,且必选 x x x的方案数。很简单的一个dp求解出来即可。令其为 d p ( x , k ) dp(x,k) dp(x,k)
a n s x = ∑ i = 1 a [ i ] ≤ x d p ( x , k ) ∗ ( n u m [ x ] − 1 ) ∏ j = n n − i j ans_x=\sum_{i=1}^{a[i]\leq x}\frac{dp(x,k)*(num[x]-1)}{\prod_{j=n}^{n-i}j} ansx=∑i=1a[i]≤x∏j=nn−ijdp(x,k)∗(num[x]−1)
答案累和即可。
AC代码:
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define pii pair<int,int>
#define pb push_back
#define mp make_pair
#define vi vector<int>
#define vll vector<ll>
const int maxn = 5000 + 5;
const int mod = 998244353;
int a[maxn];
ll ksm (ll a , ll b){
ll ans = 1 , base = a;
while (b){
if (b & 1) ans = ans * base % mod;b >>= 1;base = base * base % mod;}return ans;}
ll dp[maxn][maxn] , inv[maxn];
int main()
{
ios::sync_with_stdio(false);
int n; cin >> n;
int m = n;
for (int i = 1 ; i <= n ; i++) inv[i] = ksm(i , mod - 2);
map<int,ll> q;
for (int i = 1 ; i <= n ; i++){
cin >> a[i];
q[a[i]]++;
}
sort(a + 1 , a + 1 + n);
n = unique(a + 1 , a + 1 + n) - a - 1;
//reverse(a + 1 , a + 1 + n);
dp[0][0] = 1;
for (int i = 1 ; i <= n ; i++){
dp[i][0] = 1;
for (int j = 1 ; j <= i ; j++){
dp[i][j] = (dp[i - 1][j] + q[a[i]] * dp[i - 1][j - 1]%mod)%mod;
}
}
ll ans = 0;
for (int i = 1 ; i <= n ; i++){
ll d , res , fm = inv[m];
for (int j = 1 ; j <= i ; j++){
fm = fm * inv[m - j] %mod;
d = (dp[i][j] - dp[i - 1][j] + mod) % mod;
res = fm * d %mod;
res = res * (q[a[i]] - 1) % mod;
ans = (ans + res) % mod;
}
}
cout << ans << endl;
return 0;
}