【前言】
组队训练的第一场比赛,感觉这场出题十分阴间,后面几个乱搞题根本不会.jpg

赛时只过了5题,rk123,学校参加5/8。

A. Alice and Bob

【题意】

两人博弈,每次一个人从一堆中拿出kkk个,同时从另一堆中拿出ks(s≥0)ks(s\geq0)ks(s≥0)个,问谁先不能拿。

T≤10000,n≤5000T\leq 10000,n\leq 5000T≤10000,n≤5000

【思路】

首先我们可以考虑暴力SG。

设sg[i][j]sg[i][j]sg[i][j]表示第一堆为iii,第二堆为jjj时的SG函数,直接模拟取石子的转移,复杂度是O(n3log⁡n)O(n^3\log n)O(n3logn)的,跑一段时间就可以跑完n≤5000n\leq 5000n≤5000,赛时我们直接打表就过了。

事实上,可以发现,对于每个iii,最多存在一个jjj,使得(i,j)(i,j)(i,j)是后手胜,这个结论可以用反证法证明。

知道这个结论以后,我们可以记录所有后手胜的pair,这样推导就是O(n2log⁡n)O(n^2\log n)O(n2logn)了,但由于无用状态很多,实际上速度近似O(n)O(n)O(n),这样打表也会更快。

【参考代码】*

#include <bits/stdc++.h>using namespace std;const int MAXN = 5005;bitset<MAXN> F[MAXN];int main()
{int cnt = 0;for (int SUM = 0;SUM <= 10000;SUM++)for (int i = max(0,SUM - 5000),j = SUM - i;i <= 5000 && j >= 0;i++,j--)if (!F[i][j]){for (int k = 1;i + k <= 5000;k++)for (int l = 0;j + k * l <= 5000;l++)F[i + k][j + k * l] = 1;for (int k = 1;j + k <= 5000;k++)for (int l = 0;i + k * l <= 5000;l++)F[i + k * l][j + k] = 1;}int T;cin >> T;while (T--){int n,m;cin >> n >> m;puts(F[n][m] ? "Alice" : "Bob");}return 0;
}

B. Ball Dropping

【题意】

一个圆卡在一个等腰直角梯形内部,求球心到底部的距离。

【思路】

简单计算几何。

【参考代码】

#include<bits/stdc++.h>
#define rep(i,a,b) for(int i=(a),i##ss=(b);i<=i##ss;i++)
#define dwn(i,a,b) for(int i=(a),i##ss=(b);i>=i##ss;i--)
#define deb(x) cerr<<(#x)<<":"<<(x)<<'\n'
#define pb push_back
#define fi first
#define se second
#define hvie '\n'
using namespace std;
typedef pair<int,int> pii;
typedef long long ll;
typedef unsigned long long ull;
typedef double db;
int yh(){int ret=0;bool f=0;char c=getchar();while(!isdigit(c)){if(c==EOF)return -1;if(c=='-')f=1;c=getchar();}while(isdigit(c))ret=(ret<<3)+(ret<<1)+(c^48),c=getchar();return f?-ret:ret;
}
const int maxn=3e5+5;int main(){double r,a,b,h;cin>>r>>a>>b>>h;if(r*2<=b){puts("Drop");return 0;}puts("Stuck");double ta=2*h/(a-b);double si2=(ta*ta/(1+ta*ta));double temp1=r*sqrt(si2);double d_tan=temp1-b/2;double d=d_tan*ta;double co2=1/(ta*ta+1);double y=r*sqrt(co2)+d;cout<<fixed<<setprecision(10)<<y<<hvie;return 0;
}

C. Cut the Tree

【题意】

给定一个带点权的树,可以删去树上一个点,最小化所有子树最长上升子序列长度的最大值。

n≤105n\leq 10^5n≤105

【思路】

首先如何求一棵树的最长上升子序列的长度?

这个可以通过线段树合并来O(nlog⁡n)O(n\log n)O(nlogn)做,具体来说,每个点维护两个数组f,gf,gf,g,分别表示子树内,结尾权值为iii时的最长上升和下降子序列长度。自底向上线段树合并并维护答案即可。

接下来考虑删点模型:

  • 先在原树上求出最长链,要想答案更优,删除的点必须是链上的点,因此可以尝试删除链的中点,再求一条最长链。

  • 要想答案比之前都更优,则删除的点必须在之前所有答案链的交集内。

因为若干条链的交集一定还是一条链,所以可以类似二分一样,继续尝试删除链的中点,再求一条最长链。重复此操作直到所有答案链的交集为空,最多需要求O(log⁡n)O(\log n)O(logn) 次,时间复杂度 O(nlog⁡2n)O(n \log^2 n)O(nlog2n) 。

该模型的思考

满足以下性质的黑盒F(S)F(S)F(S)可以套用这个删点模型。

  • F(S)F(S)F(S)求的是树SSS里某条链的函数最大值。
  • FFF满足:若TTT是SSS的子树,则F(T)≤F(S)F(T)\leq F(S)F(T)≤F(S)。

复杂度O(nlog⁡2n)O(n\log ^2 n)O(nlog2n)

【参考代码】(std)

#include <bits/stdc++.h>const int N = 100010, L = 20;using Info = std::pair<int, int>;const Info BAD(0, 0);struct Node {Info in, de;int ls, rs;
} P[N * L];int top;
void clear() {top = 0;memset(P, 0, sizeof(P));
}
int clone(int o) {int p = ++top;memcpy(P + p, P + o, sizeof(Node));return p;
}
void upd(int o) {P[o].in = std::max(P[P[o].ls].in, P[P[o].rs].in);P[o].de = std::max(P[P[o].ls].de, P[P[o].rs].de);
}
Info qin(int o, int l, int r, int k) {if (!o || k < l) return BAD;if (r <= k) return P[o].in;int mid = (l + r) / 2;return std::max(qin(P[o].ls, l, mid, k), qin(P[o].rs, mid + 1, r, k));
}
Info qde(int o, int l, int r, int k) {if (!o || k > r) return BAD;if (k <= l) return P[o].de;int mid = (l + r) / 2;return std::max(qde(P[o].ls, l, mid, k), qde(P[o].rs, mid + 1, r, k));
}
int ins(int o, int l, int r, int k, const Info& in, const Info& de) {o = clone(o);if (l == r) {P[o].in = in;P[o].de = de;return o;}int mid = (l + r) / 2;if (k <= mid) P[o].ls = ins(P[o].ls, l, mid, k, in, de);else P[o].rs = ins(P[o].rs, mid + 1, r, k, in, de);upd(o);return o;
}int n;
std::vector<int> g[N];
int a[N];struct Solve {int root, curu;Info inc[N], dec[N];int sa[N];struct {int len, lca, x, y;} diam;void test(Info in, Info de, int lca) {int len = in.first + de.first + (lca != 0);sa[curu] = std::max(sa[curu], len);if (diam.len < len)diam = {len, lca, in.second, de.second};}int merge(int p, int q, int l, int r) {if (!p || !q) return p ^ q;if (l == r) {P[p].in = std::max(P[p].in, P[q].in);P[p].de = std::max(P[p].de, P[q].de);return p;}test(P[P[p].ls].in, P[P[q].rs].de, 0);test(P[P[q].ls].in, P[P[p].rs].de, 0);int mid = (l + r) / 2;P[p].ls = merge(P[p].ls, P[q].ls, l, mid);P[p].rs = merge(P[p].rs, P[q].rs, mid + 1, r);upd(p);return p;}int dfs(int u, int p) {int rt = 0;Info iin = BAD, dde = BAD;{curu = u; test(BAD, BAD, u);}for (int v : g[u]) if (v != p) {int sub = dfs(v, u);sa[u] = std::max(sa[u], sa[v]);Info rin = qin(rt, 1, n, a[u] - 1), rde = qde(rt, 1, n, a[u] + 1),sin = qin(sub, 1, n, a[u] - 1), sde = qde(sub, 1, n, a[u] + 1);curu = u;test(rin, sde, u);test(sin, rde, u);iin = std::max(iin, sin);dde = std::max(dde, sde);rt = merge(rt, sub, 1, n);}++iin.first; inc[u] = iin;++dde.first; dec[u] = dde;iin.second = dde.second = u;rt = ins(rt, 1, n, a[u], iin, dde);
//      std::cerr << root << " -> " << u << " = " << sa[u] << "\n";return rt;}void main() {clear();dfs(root, -1);
//      std::cerr << diam.len << ", " << diam.x << ' ' << diam.y << ' ' << diam.lca << '\n';}
} fir, le, ri;int prt[N];
void dfs(int u) {for (int v : g[u]) if (v != prt[u]) {prt[v] = u;dfs(v);}
}int main() {std::ios::sync_with_stdio(false);std::cin.tie(NULL);std::cin >> n;for (int rep = 1; rep != n; ++rep) {int u, v; std::cin >> u >> v;g[u].push_back(v);g[v].push_back(u);}for (int i = 1; i <= n; ++i) std::cin >> a[i];fir.root = 1;fir.main();
/*  if (fir.diam.len == 1) {std::cout << "1\n";return 0;}*/int x = fir.diam.x, y = fir.diam.y;if (!x) x = fir.diam.lca;else while (fir.inc[x].second) x = fir.inc[x].second;if (!y) y = fir.diam.lca;else while (fir.dec[y].second) y = fir.dec[y].second;le.root = x; ri.root = y;
//  std::cerr << "From " << x << " to " << y << '\n';le.main(); ri.main();prt[x] = -1; dfs(x);int ans = fir.diam.len;for (int v = y; v != -1; v = prt[v]) {int mx = 0;for (int u : g[v]) {if (u == prt[v]) mx = std::max(mx, ri.sa[u]);else mx = std::max(mx, le.sa[u]);}ans = std::min(ans, mx);}std::cout << ans << '\n';return 0;
}

D. Determine the Photo Position

【题意】

给出一个n×nn\times nn×n的01矩阵,用一个1×m1\times m1×m的矩阵去覆盖一段0,求方案数

n≤2000n\leq 2000n≤2000

【思路】

略。

复杂度O(n2)O(n^2)O(n2)

【参考代码】

/** @date:2021-07-17 12:05:55* @source:
*/
#include <bits/stdc++.h>using namespace std;typedef pair<int, int> pii;
typedef long long ll;
typedef pair<ll, ll> pll;
typedef vector<int> vi;
#define fir first
#define sec second
#define ALL(x) (x).begin(), (x).end()
#define SZ(x) (int)x.size()
#define For(i, x) for (int i = 0; i < (x); ++i)
#define Trav(i, x) for (auto & i : x)
#define pb push_back
template<class T, class G> bool chkMax(T &x, G y) {return y > x ? x = y, 1 : 0;}
template<class T, class G> bool chkMin(T &x, G y) {return y < x ? x = y, 1 : 0;}const int MAXN = 2000 + 5;int N, M;
char S[MAXN], T[MAXN];int main() {scanf("%d%d", &N, &M);int ans = 0;for (int i = 0; i < N; ++i) {scanf("%s", S);int st = 0;for (int j = 0; j < N; ++j) {if (S[j] == '1') {st = j + 1;}if (j - st + 1 >= M) ++ans;}}printf("%d\n", ans);return 0;
}

E. scape along Water Pipes

【题意】

给出一个n×mn\times mn×m的水管图,从(1,1)(1,1)(1,1)走到(n,m)(n,m)(n,m),每走一步前,可以选择一个管道集合旋转相同的角度,要求在20nm20nm20nm步内走到终点或输出无解。(水管是四种直角和两种直的)

n,m≤1000n,m\leq 1000n,m≤1000

【思路】

首先,旋转是任意的,那么我们其实不需要关注现在的情况,其实8nm8nm8nm就是答案的上界了,所以集合选取是没有意义的,我们只需要考虑旋转下一个要去的格子就行。

那么只需要对所有状态bfs就行了。

复杂度O(nm)O(nm)O(nm)

【参考代码】(std)

#include<bits/stdc++.h>
using namespace std;int read(){int a = 0; char c = getchar(); while(!isdigit(c)) c = getchar();while(isdigit(c)){a = a * 10 + c - 48; c = getchar();} return a;
}const int _ = 1003 , dir[4][2] = {1,0,-1,0,0,1,0,-1};
int Map[1003][1003] , N , M , T , pre[1003][1003][4];
struct dat{int x , y , dir;}; queue < dat > q;
#define id(k,j,i) ((i) * 1001 * 1001 + (j) * 1001 + (k))vector < pair < int , int > > node;
void outputanswer(int p , int q , int r){if(!~pre[p][q][r]){puts("YES"); node.push_back(make_pair(p , q)); return;}int x = pre[p][q][r] % 1001 , y = pre[p][q][r] / 1001 % 1001 , d = pre[p][q][r] / 1001 / 1001;outputanswer(x , y , d); node.push_back(make_pair(p , q));
}int check(pair < int , int > x , pair < int , int > y){for(int i = 0 ; i < 4 ; ++i){int p = x.first + dir[i][0] , q = x.second + dir[i][1];if(make_pair(p , q) == y) return i;}assert(0); return 0;
}int check_type(int p , int q){if(p == q) return p < 2 ? 5 : 4;switch(p){case 0: return q == 2 ? 1 : 0;case 1: return q == 2 ? 2 : 3;case 2: return q == 0 ? 3 : 0;case 3: return q == 0 ? 2 : 1;}
}void output(pair < int , int > now , pair < int , int > pre , pair < int , int > suf){int p = check_type(check(pre , now) , check(now , suf)) , x = now.first , y = now.second;int deg = p >= 4 ? abs(p - Map[x][y]) * 90 : (p - Map[x][y] + 4) % 4 * 90; Map[x][y] = p;printf("1 %d %d %d\n0 %d %d\n" , deg , x , y , x , y);
}bool push(int x , int y , int d , int prep){int preid = id(x , y , prep); x += dir[d][0]; y += dir[d][1];if(x && x <= N && y && y <= M && !pre[x][y][d]){pre[x][y][d] = preid; q.push((dat){x , y , d}); return 0;}else if(x == N + 1 && y == M){node.clear(); node.push_back(make_pair(0 , 1));outputanswer(x - dir[d][0] , y - dir[d][1] , prep); node.push_back(make_pair(N + 1 , M));cout << 2 * (node.size() - 2) << endl;for(int i = 1 ; i + 1 < node.size() ; ++i)  output(node[i] , node[i - 1] , node[i + 1]);return 1;}return 0;
}void solve(){N = read(); M = read(); while(!q.empty()) q.pop();for(int i = 1 ; i <= N ; ++i)for(int j = 1 ; j <= M ; ++j){Map[i][j] = read(); memset(pre[i][j] , 0 , sizeof(pre[i][j]));}q.push((dat){1 , 1 , 0}); pre[1][1][0] = -1;while(!q.empty()){int x = q.front().x , y = q.front().y , d = q.front().dir; q.pop();if(Map[x][y] <= 3){if(push(x , y , d ^ 2 , d) || push(x , y , d ^ 3 , d)) return;}else if(push(x , y , d , d)) return;}puts("NO");
}int main(){for(T = read() ; T ; --T){solve();} return 0;}

F. Find 3-friendly Numbers

【题意】

定义一个数是好的,如果它存在一个子串(允许前导零)是3的倍数。

TTT组数据,求[L,R][L,R][L,R]中好的数的个数。

$T\leq 10000,1\leq L,R\leq 10^{18} $

【思路】

首先数位DP是可以做的,比如记录到某个位置iii时,sum[j][i]%3=0/1/2sum[j][i]\% 3=0/1/2sum[j][i]%3=0/1/2能否满足,其中jjj是iii之前的某个位置。

但是这样太麻烦了,稍加观察就可以发现,只要位数不少于3位,这个数必然是合法的。

所以我们只需要暴力出n<100n<100n<100的结果即可。

复杂度O(T)O(T)O(T)

【参考代码】

/** @date:2021-07-17 12:12:04* @source:
*/
#include <bits/stdc++.h>using namespace std;typedef pair<int, int> pii;
typedef long long ll;
typedef pair<ll, ll> pll;
typedef vector<int> vi;
#define fir first
#define sec second
#define ALL(x) (x).begin(), (x).end()
#define SZ(x) (int)x.size()
#define For(i, x) for (int i = 0; i < (x); ++i)
#define Trav(i, x) for (auto & i : x)
#define pb push_back
template<class T, class G> bool chkMax(T &x, G y) {return y > x ? x = y, 1 : 0;}
template<class T, class G> bool chkMin(T &x, G y) {return y < x ? x = y, 1 : 0;}int T;
long long L, R;
int F[100];long long f(long long x) {return x < 100 ? F[x] : x + F[99] - 99;
}int main() {scanf("%d", &T);for (int i = 1; i < 100; ++i) {int flag = i % 3 == 0;if (i > 10 && i / 10 % 3 == 0) flag = 1;if (i % 10 % 3 == 0) flag = 1;F[i] = F[i - 1] + flag;}while (T--) {scanf("%lld%lld", &L, &R);printf("%lld\n", f(R) - f(L - 1));}return 0;
}

G. Game of Swapping Numbers

【题意】

给定序列A,BA,BA,B,需要交换恰好kkk次AAA中两个不同的数,使得A,BA,BA,B对应位置上的数差值的绝对值之和最大。

n≤105n\leq 10^5n≤105

【思路】

赛时不会。

最优解性质

考虑任意一个最优解,我们把交换后的数字重新放回原来的位置,相当于为每一个元素分配了它在答案中的符号。比如 A={0,3},B={1,2}A=\{0, 3\}, B = \{1, 2\}A={0,3},B={1,2},最优解符号分配是 A={−0,+3},B={−1,+2}A=\{-0,+3\}, B=\{-1,+2\}A={−0,+3},B={−1,+2}。

考察符合要求的解符号分配规则,其实只要满足 $A, B $中正号总和和负号总和相等,而 A,BA,BA,B 各自的正负号可以不一样。

注意:有可能出现正负号和实际绝对值相反的情况,但是如果交换这一对正负号,只会使得解变优,所以在题目求最优的前提下,正负号是可以随意分配的。

假设我们能任意指定$ k $来求最优解,相当于是把 $A, B 合在一起排序,取最大的合在一起排序,取最大的合在一起排序,取最大的 n 个填正号,最小的个填正号,最小的个填正号,最小的 n $个填负号即可。

最少步数得到最优解

考虑每一对元素 Ai,BiA_i,B_iAi​,Bi​,若它们符号不同,则直接忽略这一对元素;否则,一对都是+++的元素需要和一对都是 −-− 的元素进行交换才能尽快达到最优解。

除排序外,复杂度O(n)O(n)O(n)

【参考代码】

#include<bits/stdc++.h>
#define rep(i,a,b) for(int i=(a),i##ss=(b);i<=i##ss;i++)
#define dwn(i,a,b) for(int i=(a),i##ss=(b);i>=i##ss;i--)
#define deb(x) cerr<<(#x)<<":"<<(x)<<'\n'
#define pb push_back
#define fi first
#define se second
#define hvie '\n'
using namespace std;
typedef pair<int,int> pii;
typedef long long ll;
typedef unsigned long long ull;
typedef double db;
int yh(){int ret=0;bool f=0;char c=getchar();while(!isdigit(c)){if(c==EOF)return -1;if(c=='-')f=1;c=getchar();}while(isdigit(c))ret=(ret<<3)+(ret<<1)+(c^48),c=getchar();return f?-ret:ret;
}
const int maxn=5e5+5;
int a[maxn],b[maxn];
int A[maxn],B[maxn];
int n,k;
int main(){n=yh(),k=yh();rep(i,1,n) a[i]=yh();ll ans=0;rep(i,1,n){b[i]=yh();A[i]=2*min(a[i],b[i]);B[i]=-2*max(a[i],b[i]);ans+=abs(a[i]-b[i]);}sort(A+1,A+1+n,greater<int>());sort(B+1,B+1+n,greater<int>());for(int i=1;i<=k&&i<=n&&A[i]+B[i]>0;i++) ans+=B[i]+A[i];cout<<ans<<hvie;return 0;
}

H. Hash Funtion

【思路】

给定nnn个互不相同的数,找一个最小的数mmm使得他们在模mmm意义下互不相同。

n≤5×105n\leq 5\times 10^5n≤5×105

【思路】

赛时数据弱,暴力可过。

考虑正解,两个数a,ba,ba,b模mmm余数相同,当且仅当∣a−b∣|a-b|∣a−b∣是mmm的约数。

问题转化为找到最小的mmm使得它不是任意一个∣ai−aj∣|a_i-a_j|∣ai​−aj​∣的约数,而我们只需要知道每一个差值,就可以通过枚举mmm以及它的倍数,在O(nlog⁡n)O(n\log n)O(nlogn)的时间内找到最优值。

这是一个典中典的问题,我们只需要设PiP_iPi​为数值iii是否存在,将它与PMX−iP_{MX-i}PMX−i​卷积,判断对应位置是否大于0即可。

复杂度O(nlog⁡n)O(n\log n)O(nlogn)

【参考代码】

#include<bits/stdc++.h>
#define pb push_back
#define mkp make_pair
#define fi first
#define se second
using namespace std;typedef double db;
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<int,ll> pil;
const int N=11e5+10,mod=998244353,G=3;
const int MX=500002,inf=0x3f3f3f3f;int read()
{int ret=0,f=1;char c=getchar();while(!isdigit(c)) {if(c=='-')f=0;c=getchar();}while(isdigit(c)) ret=ret*10+(c^48),c=getchar();return f?ret:-ret;
}namespace Math
{int inv[N];void gmin(int &x,int y){x=min(x,y);}void gmax(int &x,int y){x=max(x,y);}int upm(int x){return x>=mod?x-mod:(x<0?x+mod:x);}void up(int &x,int y){x=upm(x+y);}int mul(int x,int y){return 1ll*x*y%mod;}int qpow(int x,int y){int res=1;for(;y;y>>=1,x=mul(x,x))if(y&1)res=mul(res,x);return res;}int getinv(int x){return qpow(x,mod-2);}void initinv(){inv[1]=1;for(int i=2;i<N;++i) inv[i]=mod-mul(mod/i,inv[mod%i]);}pii mul(pii x,pii y,int val){return mkp(upm(mul(x.fi,y.fi)+mul(mul(x.se,y.se),val)),upm(mul(x.fi,y.se)+mul(x.se,y.fi)));}int qpow(pii x,int y,int val){pii res=mkp(1,0);for(;y;y>>=1,x=mul(x,x,val))if(y&1)res=mul(res,x,val);return res.fi;}int calcCipolla(int x){int y=rand()%mod;while(qpow(upm(mul(y,y)-x),(mod-1)/2)!=mod-1) y=rand()%mod;return qpow(mkp(y,1),(mod+1)/2,upm(mul(y,y)-x));}
}
using namespace Math;namespace Poly
{int m,L,rev[N];void ntt(int *a,int n,int f){for(int i=0;i<n;++i) if(i<rev[i]) swap(a[i],a[rev[i]]);for(int i=1;i<n;i<<=1){int wn=qpow(G,(mod-1)/(i<<1));if(!~f) wn=getinv(wn);for(int j=0;j<n;j+=i<<1){int w=1;for(int k=0;k<i;++k,w=mul(w,wn)){int x=a[j+k],y=mul(w,a[i+j+k]);a[j+k]=upm(x+y);a[i+j+k]=upm(x-y);}}}if(!~f) for(int i=0,inv=getinv(n);i<n;++i) a[i]=mul(a[i],inv);}void reget(int n){for(m=1,L=0;m<=n;m<<=1,++L);for(int i=0;i<m;++i) rev[i]=(rev[i>>1]>>1)|((i&1)<<(L-1));}void polymul(int *a,int *b,int *c){static int A[N],B[N];copy(a,a+m,A);copy(b,b+m,B);ntt(A,m,1);ntt(B,m,1);for(int i=0;i<m;++i) c[i]=mul(A[i],B[i]);ntt(c,m,-1);fill(A,A+m,0);fill(B,B+m,0);}void polymult(int *a,int *b,int *c,int dega,int degb){reget(dega+degb);polymul(a,b,c);}
}
using namespace Poly;int a[N],b[N],c[N],d[N];int main()
{initinv();int n=read();for(int i=1;i<=n;++i){int t=read();a[t]=1;b[MX-t]=1;}polymult(a,b,c,MX,MX);for(int i=1;i<MX;++i) d[i]=c[MX-i];for(int seed=1;seed<MX;++seed){int fg=0;for(int i=1;i*seed<MX;++i) fg|=d[i*seed];if(!fg) {printf("%d\n",seed);break;}}return 0;
}

I. Increasing Subsequence

【题意】

给出一个长度为nnn的排列ppp,两个人轮流取数,每次取的数要在之前该人取的右边,且比当前取出来所有数要大。所有当前可选的数都将等概率随机被当前决策人选中。问两个人期望取的轮数。

n≤5000n\leq 5000n≤5000

【思路】

我们希望设计DP能够表示所有(i,j)(i,j)(i,j)局面出现的概率之和。

令f[i][j]f[i][j]f[i][j]表示第一个人上一轮选了iii,第二个人上一轮选了jjj,当前是第二个人选,这个局面出现的概率。g[i][j]g[i][j]g[i][j]则表示当前是第一个人选的局面出现的概率。

以 f[i][j]f[i][j]f[i][j] 转移为例,枚举一个 $k $使得 k>jk>jk>j 且 a[k]>a[i],a[k]>a[j]a[k] > a[i], a[k] > a[j]a[k]>a[i],a[k]>a[j], 则:
g[i][k]+=g[i][j]cntg[i][k]+=\frac {g[i][j]} {cnt} g[i][k]+=cntg[i][j]​
其中$ cnt 表示表示表示 j \leq l \leq n$, a[l]>a[j],a[l]>a[i]a[l] > a[j],a[l] > a[i]a[l]>a[j],a[l]>a[i] 的 lll 的数量。

直接做的复杂度是$ O(n^3 )$。

观察转移式和选数的方法,我们发现每次选择的$ k $事实上只需要关注另一个人上一个选的数的大小。于是我们稍微改变动态规划状态的含义。

令$f[i][j] 表示第一个人上一个选了表示第一个人上一个选了表示第一个人上一个选了i$,当前是第二个人选,且第二个人需要选一个位置 ≥j\ge j≥j的数,这样的局面出现的概率。

考虑新状态的转移,当 $a[j] > a[i] 时,将时,将时,将 f[i][j] 转移给转移给转移给 g[i][j],$ 同时,$f[i][j+1] 也可以从也可以从也可以从f[i][j] $转移过来。

计算答案时,当且仅当$ f[i][j]$ 转移到 g[i][j]g[i][j]g[i][j], 或 $g[i][j] $转移到 $f[i][j] $时,才将 $f[i][j] $或 $g[i][j] $计入到答案中,因为只有这样的转移表示一个新的局面形成。

对于概率的计算,可以在$ f[i][j] $转移到 g[i][j]g[i][j]g[i][j] 上时直接除以 i≤l≤ni \leq l \leq ni≤l≤n, a[l]>a[j]a[l] > a[j]a[l]>a[j] 的数量,这一数量可以通过$ O(n^2 )$ 的预处理直接完成 $O(1) $的询问。

事实上,前一个DP似乎可以通过一些奇怪的方法来做到一个可以过的复杂度。

【参考代码】

#include<bits/stdc++.h>
#define rep(i,a,b) for(int i=(a),i##ss=(b);i<=i##ss;i++)
#define dwn(i,a,b) for(int i=(a),i##ss=(b);i>=i##ss;i--)
#define deb(x) cerr<<(#x)<<":"<<(x)<<'\n'
#define pb push_back
#define fi first
#define se second
#define hvie '\n'
using namespace std;
typedef pair<int,int> pii;
typedef long long ll;
typedef unsigned long long ull;
typedef double db;
int yh(){int ret=0;bool f=0;char c=getchar();while(!isdigit(c)){if(c==EOF)return -1;if(c=='-')f=1;c=getchar();}while(isdigit(c))ret=(ret<<3)+(ret<<1)+(c^48),c=getchar();return f?-ret:ret;
}
const int maxn=5005,mod=998244353;
int mul(int a,int b){return (ll)a*b%mod;}
int add(int a,int b){return a+b>=mod?a+b-mod:a+b;}int a[maxn],sm[maxn],ct[maxn],inv[maxn];
int dp[maxn][maxn][2];
int n;int main(){n=yh();rep(i,1,n) a[i]=yh();inv[1]=1;rep(i,2,n+1) inv[i]=(mod-1ll*mod/i*inv[mod%i]%mod)%mod;dwn(i,n,0){dwn(j,n,0){if(i==j)continue;if(a[j]>a[i]){dp[i][j][0]=mul(sm[j],inv[ct[j]])+1;}}int sum=0,cnt=0;dwn(j,n,0){if(i==j)continue;if(a[i]>a[j]){dp[i][j][1]=mul(sum,inv[cnt])+1;}else{sum=add(sum,dp[i][j][0]);cnt++;}}dwn(j,n,0) if(a[i]>a[j]) sm[j]=add(sm[j],dp[i][j][1]),ct[j]++;}int ans=0;rep(i,1,n) ans=add(ans,dp[0][i][0]);cout<<mul(ans,inv[n])<<hvie;return 0;
}

J. Journey among Railway Stations

【题意】

一路上有nnn个点,每个点有一个合法的时间段[ui,vi][u_i,v_i][ui​,vi​],相邻两个点有一个距离。每次询问,在uiu_iui​的时间从iii出发,能否依次经过i+1∼ji+1\sim ji+1∼j所有点,使得到达时间满足每个点的合法时间段(如果提前到了可以等待)。同时要支持修改相邻两点的距离,或者修改一个点的合法时间段。

n,Q≤106n,Q\leq 10^6n,Q≤106

【思路】

一看就很线段树。

考虑 $(l, r) 的询问所有的限制条件。我们以的询问所有的限制条件。我们以的询问所有的限制条件。我们以 (1, 3)$ 举例,即:

$ u_1\leq v_1,\max(u_1+d_1,u_2 )\leq v_2,\max(\max(u_1+d_1,u_2 )+d_2, u_3 )\leq v_3$

现在我们设 ui′=ui+di+d(i+1)+…+d(n−1),vi′=vi+di+d(i+1)+…+d(n−1)u_i'=u_i+d_i+d_(i+1)+…+d_(n-1),v_i'=v_i+d_i+d_(i+1)+…+d_(n-1)ui′​=ui​+di​+d(​i+1)+…+d(​n−1),vi′​=vi​+di​+d(​i+1)+…+d(​n−1)

现在$ (l, r) $的限制变成了:

$ u_1’\leq v_1’,\max(u_1’,u_2’ )\leq v_2’,\max(u_1’, u_2’,u_3’ )\leq v_3$

线段树维护当前区间是否可行、$u 的最大值和的最大值和的最大值和 v $的最小值,复杂度是 O(nlog⁡n)O(n\log n)O(nlogn)。

【参考代码】

#include<bits/stdc++.h>
#define rep(i,a,b) for(int i=(a),i##ss=(b);i<=i##ss;i++)
#define dwn(i,a,b) for(int i=(a),i##ss=(b);i>=i##ss;i--)
#define deb(x) cerr<<(#x)<<":"<<(x)<<'\n'
#define pb push_back
#define fi first
#define se second
#define hvie '\n'
using namespace std;
typedef pair<int,int> pii;
typedef long long ll;
typedef unsigned long long ull;
typedef double db;
int yh(){int ret=0;bool f=0;char c=getchar();while(!isdigit(c)){if(c==EOF)return -1;if(c=='-')f=1;c=getchar();}while(isdigit(c))ret=(ret<<3)+(ret<<1)+(c^48),c=getchar();return f?-ret:ret;
}
const int maxn=1e6+5,inf=0x3f3f3f3f;
struct node{int latest_arrival_time_needed;int earliest_next_arrival_time;int sum_of_the_time_cost;
};
node operator+(node a,node b){if(a.earliest_next_arrival_time>b.latest_arrival_time_needed){return {-1,inf,inf};}return {max(min(a.latest_arrival_time_needed,b.latest_arrival_time_needed-a.sum_of_the_time_cost),-1),min(max(a.earliest_next_arrival_time+b.sum_of_the_time_cost,b.earliest_next_arrival_time),inf),min(a.sum_of_the_time_cost+b.sum_of_the_time_cost,inf)};
};
#define ls (v<<1)
#define rs (v<<1|1)
#define mid ((l+r)>>1)
int n;
node nd[maxn<<2];
int U[maxn],V[maxn],C[maxn];
void push_up(int v){nd[v]=nd[ls]+nd[rs];
}
void init(int pos){int l=1,r=n,v=1;while(l!=r){if(pos<=mid) v=ls,r=mid;else v=rs,l=mid+1;}nd[v]={V[l],min(U[l]+C[l],inf),C[l]};while(v>>=1)push_up(v);
}
node ans;
void query(int v,int l,int r,int al,int ar){if(al<=l&&ar>=r)return ans=ans+nd[v],void();if(al<=mid) query(ls,l,mid,al,ar);if(ar>mid) query(rs,mid+1,r,al,ar);
}
void build(int v,int l,int r){if(l==r) return nd[v]={V[l],U[l]+C[l],C[l]},void();build(ls,l,mid);build(rs,mid+1,r);push_up(v);
}
int main(){dwn(_,yh(),1){n=yh();rep(i,1,n) U[i]=yh();rep(i,1,n) V[i]=yh();rep(i,1,n-1) C[i]=yh();build(1,1,n);dwn(__,yh(),1){int op=yh();switch(op){case 0:{ans={inf,-inf,0};int l=yh(),r=yh();if(l==r){puts("Yes");break;}query(1,1,n,l,r-1);if(ans.earliest_next_arrival_time>V[r]){puts("No");}else puts("Yes");break;}case 1:{int x=yh();C[x]=yh();init(x);break;}case 2:{int x=yh();U[x]=yh();V[x]=yh();init(x);break;}}}}return 0;
}

K. Knowledge Test about Match

【题意】

随机生成一个权值范围为0∼n−10\sim n-10∼n−1的序列,用0∼n−10\sim n-10∼n−1去和它匹配,匹配函数是开根,要求平均情况下情况和最优偏差不超过4%

(即∑(ai−bi\sum \sqrt{(a_i-b_i}∑(ai​−bi​​)

n≤105n\leq 10^5n≤105

【思路】

赛时模拟退火了半天,-23都没过。

根号的凹凸性和常见函数相反,所以这题很难不通过匹配去求最优解。

主要要强调的是 sort,直接 sort 其实是一个很糟糕的举动,因为 sqrt 的导函数随着xxx的增大值越来越小,而你 sort 后很可能是层次不齐的情况,其实反而增大了函数和。

举一个例子:{1,2,3}, {0,1,2},(1,1), (2,2), (0, 3) 会比 sort 的结果好很多。

在题目保证数据随机的情况下,本地可以模拟实际数据并测试正确率。

由于 KM 的复杂度是O(N3)O(N^3 )O(N3),可以选择小数据 KM 大数据贪心,以最优化平均偏差。

一个可行的贪心做法:从小到大枚举 ddd,每次贪心去看是否存在两数差=d=d=d的数对,如果存在就暴力匹配上去。

还有一种奇怪的做法:

【参考代码】

#include<bits/stdc++.h>
#define rep(i,a,b) for(int i=(a),i##ss=(b);i<=i##ss;i++)
#define dwn(i,a,b) for(int i=(a),i##ss=(b);i>=i##ss;i--)
#define deb(x) cerr<<(#x)<<":"<<(x)<<'\n'
#define pb push_back
#define fi first
#define se second
#define hvie '\n'
using namespace std;
typedef pair<int,int> pii;
typedef long long ll;
typedef unsigned long long ull;
typedef double db;
int yh(){int ret=0;bool f=0;char c=getchar();while(!isdigit(c)){if(c==EOF)return -1;if(c=='-')f=1;c=getchar();}while(isdigit(c))ret=(ret<<3)+(ret<<1)+(c^48),c=getchar();return f?-ret:ret;
}
const int maxn=3e5+5;int main(){dwn(_,yh(),1){int n=yh();vector<int>cnt(n,0),ans(n,-1);rep(i,0,n-1) cnt[yh()]++;rep(i,0,n-1){rep(j,i,n-1){if(ans[j]==-1&&cnt[j-i]){ans[j]=j-i;cnt[j-i]--;}}rep(j,0,n-1-i){if(ans[j]==-1&&cnt[j+i]){ans[j]=j+i;cnt[j+i]--;}}}rep(i,0,n-1){cout<<ans[i]<<" ";}cout<<hvie;}return 0;
}

【多校训练】2021牛客多校第一场相关推荐

  1. LCS(2021牛客多校4)

    LCS(2021牛客多校4) 题意: 让你构造三个字符串s1,s2,s3,长度均为n,要求LCS(s1,s2)=a,LCS(s2,s3)=b,LCS(s1,s3)=c 题解: 先考虑三个串互相LCS为 ...

  2. 【2021牛客多校2】F-Girlfriend 计算几何

    2021牛客多校2-F F-Girlfriend 题目大意 给出四个点 A , B , C , D A, B, C, D A,B,C,D 另有两点 P 1 , P 2 P_1, P_2 P1​,P2​ ...

  3. 【2021牛客寒假第五场】B-比武招亲(上)排列组合

    [2021牛客寒假第五场]B-比武招亲(上)排列组合 题意 思路 Code(44MS) 传送门: https://ac.nowcoder.com/acm/contest/9985/B 题意 思路 考 ...

  4. 【2021牛客寒假第五场】C-比武招亲(下)欧拉降幂+多项式求逆预处理伯努利数计算等幂求和

    [2021牛客寒假第五场]C-比武招亲(下)欧拉降幂+多项式求逆预处理伯努利数计算等幂求和 前置技能 题意 思路 Code(715MS) 传送门: https://ac.nowcoder.com/ac ...

  5. K-Stack 2021牛客多校2

    链接:https://ac.nowcoder.com/acm/contest/11253/K 来源:牛客网 题目描述 ZYT had a magic permutation a1,a2,⋯ ,an a ...

  6. 2021牛客多校第八场补题 D-OR

    链接:https://ac.nowcoder.com/acm/contest/11259/D 来源:牛客网 题目描述 There are two sequences of length n−1n-1n ...

  7. 2021牛客多校第五场补题

    B-Boxes 链接:https://ac.nowcoder.com/acm/contest/11256/B 来源:牛客网 题目描述 There're nn_{}n​ boxes in front o ...

  8. 【多校训练】2021牛客多校5

    [前言] 很久没有时间整理题解了,补题和打游戏的时间居多(doge) 这场其实主要F出题人数据有锅,花太多时间了(赛后重测是一血),然后后面G想歪了爆搜剪枝没过,I的回滚莫队队友前一天写了结果今天写不 ...

  9. 【多校训练】2021牛客多校第二场

    [前言] 这是打的第二场,rk39,但是AB这两个比较简单的题都没做emm,大概还是磨合的不够.然后感觉对于阈值类的东西还不是很敏感,应该看到不太好做就直接去想这种阈值的.校内3/9(然后就开启了常年 ...

最新文章

  1. Java并发编程:CountDownLatch、CyclicBarrier和 Semaphore
  2. python安装numpy-如何为python安装numpy和scipy?
  3. 【简洁+注释】剑指 Offer 32 - II. 从上到下打印二叉树 II
  4. [每日一题jQuery] jQuery选择器总结:进一步过滤、同级操作、后代操作
  5. JS_typeof()函数返回类型总结
  6. linux环境根据pid查看堆栈大小,linux - 通过PID获取过程的机器代码,而无需附加调试器 - 堆栈内存溢出...
  7. axios上传图片到php报500,vue项目中使用axios上传图片等文件
  8. 手机应用的照相录像功能,为什么转到后台就不能工作了?
  9. 什么是webpack? ----(webpack入门)
  10. python贪吃蛇_python贪吃蛇
  11. 算法-经典趣题-三色旗
  12. 你会他乡遇故知?-让自己慢下来(51)
  13. Nature:基于宏基因组测序构建人类肠道微生物组参考基因集
  14. forEach、for…in、 for…of 的区别
  15. ARM NEON优化3.RGB Packed转RGB Planar
  16. LORA手持机便携终端PDA的应用场景
  17. 2--java面向对象语法学习(部分1-变量,重载,重写)
  18. Windchill:oid获取对象、对象获取oid
  19. 摘《阿里巴巴JAVA开发手册》易错题目
  20. 景安 虚拟主机 自有SSL证书

热门文章

  1. 计算机组成原理——指令系统(课程笔记)
  2. 在html中加入一个动态图,图片上加gif图片 图片某个角落贴个gif动态图,如何在静态图片上面加一张GIF动态图...
  3. Macbook Pro(无法完全)通过序列号与激活时间辨别是否为翻新机
  4. VsCode新建VueJs项目
  5. 2022 精心整理的 C语言/C++ 语言学习宝藏,值得收藏~
  6. 抖音App四神算法分析
  7. 保研/面试复习-数据结构与算法-万字总结(近三万字)
  8. 我的世界服务器自建主城,我的世界服务器
  9. H5移动端前置摄像头成像方向错误,横屏方向错误
  10. 【分享】“飞书自建“在集简云平台集成应用的常见问题与解决方案