【前言】
组队训练的第一场比赛,感觉这场出题十分阴间,后面几个乱搞题根本不会.jpg
赛时只过了5题,rk123,学校参加5/8。
A. Alice and Bob
【题意】
两人博弈,每次一个人从一堆中拿出 k k k个,同时从另一堆中拿出 k s ( s ≥ 0 ) ks(s\geq0) ks(s≥0)个,问谁先不能拿。
T ≤ 10000 , n ≤ 5000 T\leq 10000,n\leq 5000 T≤10000,n≤5000
【思路】
首先我们可以考虑暴力SG。
设 s g [ i ] [ j ] sg[i][j] sg[i][j]表示第一堆为 i i i,第二堆为 j j j时的SG函数,直接模拟取石子的转移,复杂度是 O ( n 3 log n ) O(n^3\log n) O(n3logn)的,跑一段时间就可以跑完 n ≤ 5000 n\leq 5000 n≤5000,赛时我们直接打表就过了。
事实上,可以发现,对于每个 i i i,最多存在一个 j j j,使得 ( i , j ) (i,j) (i,j)是后手胜,这个结论可以用反证法证明。
知道这个结论以后,我们可以记录所有后手胜的pair,这样推导就是 O ( n 2 log 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 ≤ 1 0 5 n\leq 10^5 n≤105
【思路】
首先如何求一棵树的最长上升子序列的长度?
这个可以通过线段树合并来 O ( n log n ) O(n\log n) O(nlogn)做,具体来说,每个点维护两个数组 f , g f,g f,g,分别表示子树内,结尾权值为 i i i时的最长上升和下降子序列长度。自底向上线段树合并并维护答案即可。
接下来考虑删点模型:
-
先在原树上求出最长链,要想答案更优,删除的点必须是链上的点,因此可以尝试删除链的中点,再求一条最长链。
-
要想答案比之前都更优,则删除的点必须在之前所有答案链的交集内。
因为若干条链的交集一定还是一条链,所以可以类似二分一样,继续尝试删除链的中点,再求一条最长链。重复此操作直到所有答案链的交集为空,最多需要求 O ( log n ) O(\log n) O(logn) 次,时间复杂度 O ( n log 2 n ) O(n \log^2 n) O(nlog2n) 。
该模型的思考
满足以下性质的黑盒 F ( S ) F(S) F(S)可以套用这个删点模型。
- F ( S ) F(S) F(S)求的是树 S S S里某条链的函数最大值。
- F F F满足:若 T T T是 S S S的子树,则 F ( T ) ≤ F ( S ) F(T)\leq F(S) F(T)≤F(S)。
复杂度 O ( n log 2 n ) 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 × n n\times n n×n的01矩阵,用一个 1 × m 1\times m 1×m的矩阵去覆盖一段0,求方案数
n ≤ 2000 n\leq 2000 n≤2000
【思路】
略。
复杂度 O ( n 2 ) 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 × m n\times m n×m的水管图,从 ( 1 , 1 ) (1,1) (1,1)走到 ( n , m ) (n,m) (n,m),每走一步前,可以选择一个管道集合旋转相同的角度,要求在 20 n m 20nm 20nm步内走到终点或输出无解。(水管是四种直角和两种直的)
n , m ≤ 1000 n,m\leq 1000 n,m≤1000
【思路】
首先,旋转是任意的,那么我们其实不需要关注现在的情况,其实 8 n m 8nm 8nm就是答案的上界了,所以集合选取是没有意义的,我们只需要考虑旋转下一个要去的格子就行。
那么只需要对所有状态bfs就行了。
复杂度 O ( n m ) 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的倍数。
T T T组数据,求 [ L , R ] [L,R] [L,R]中好的数的个数。
$T\leq 10000,1\leq L,R\leq 10^{18} $
【思路】
首先数位DP是可以做的,比如记录到某个位置 i i i时, s u m [ j ] [ i ] % 3 = 0 / 1 / 2 sum[j][i]\% 3=0/1/2 sum[j][i]%3=0/1/2能否满足,其中 j j j是 i i i之前的某个位置。
但是这样太麻烦了,稍加观察就可以发现,只要位数不少于3位,这个数必然是合法的。
所以我们只需要暴力出 n < 100 n<100 n<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 , B A,B A,B,需要交换恰好 k k k次 A A A中两个不同的数,使得 A , B A,B A,B对应位置上的数差值的绝对值之和最大。
n ≤ 1 0 5 n\leq 10^5 n≤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 , B A,B A,B 各自的正负号可以不一样。
注意:有可能出现正负号和实际绝对值相反的情况,但是如果交换这一对正负号,只会使得解变优,所以在题目求最优的前提下,正负号是可以随意分配的。
假设我们能任意指定$ k $来求最优解,相当于是把 $A, B 合 在 一 起 排 序 , 取 最 大 的 合在一起排序,取最大的 合在一起排序,取最大的 n 个 填 正 号 , 最 小 的 个填正号,最小的 个填正号,最小的 n $个填负号即可。
最少步数得到最优解
考虑每一对元素 A i , B i A_i,B_i Ai,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
【思路】
给定 n n n个互不相同的数,找一个最小的数 m m m使得他们在模 m m m意义下互不相同。
n ≤ 5 × 1 0 5 n\leq 5\times 10^5 n≤5×105
【思路】
赛时数据弱,暴力可过。
考虑正解,两个数 a , b a,b a,b模 m m m余数相同,当且仅当 ∣ a − b ∣ |a-b| ∣a−b∣是 m m m的约数。
问题转化为找到最小的 m m m使得它不是任意一个 ∣ a i − a j ∣ |a_i-a_j| ∣ai−aj∣的约数,而我们只需要知道每一个差值,就可以通过枚举 m m m以及它的倍数,在 O ( n log n ) O(n\log n) O(nlogn)的时间内找到最优值。
这是一个典中典的问题,我们只需要设 P i P_i Pi为数值 i i i是否存在,将它与 P M X − i P_{MX-i} PMX−i卷积,判断对应位置是否大于0即可。
复杂度 O ( n log 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
【题意】
给出一个长度为 n n n的排列 p p p,两个人轮流取数,每次取的数要在之前该人取的右边,且比当前取出来所有数要大。所有当前可选的数都将等概率随机被当前决策人选中。问两个人期望取的轮数。
n ≤ 5000 n\leq 5000 n≤5000
【思路】
我们希望设计DP能够表示所有 ( i , j ) (i,j) (i,j)局面出现的概率之和。
令 f [ i ] [ j ] f[i][j] f[i][j]表示第一个人上一轮选了 i i i,第二个人上一轮选了 j j j,当前是第二个人选,这个局面出现的概率。 g [ i ] [ j ] g[i][j] g[i][j]则表示当前是第一个人选的局面出现的概率。
以
f
[
i
]
[
j
]
f[i][j]
f[i][j] 转移为例,枚举一个 $k $使得
k
>
j
k>j
k>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
]
c
n
t
g[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] 的
l
l
l 的数量。
直接做的复杂度是$ 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 ≤ n i \leq l \leq n i≤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
【题意】
一路上有 n n n个点,每个点有一个合法的时间段 [ u i , v i ] [u_i,v_i] [ui,vi],相邻两个点有一个距离。每次询问,在 u i u_i ui的时间从 i i i出发,能否依次经过 i + 1 ∼ j i+1\sim j i+1∼j所有点,使得到达时间满足每个点的合法时间段(如果提前到了可以等待)。同时要支持修改相邻两点的距离,或者修改一个点的合法时间段。
n , Q ≤ 1 0 6 n,Q\leq 10^6 n,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$
现在我们设 u i ′ = u i + d i + d ( i + 1 ) + … + d ( n − 1 ) , v i ′ = v i + d i + 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 ( n log 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 − 1 0\sim n-1 0∼n−1的序列,用 0 ∼ n − 1 0\sim n-1 0∼n−1去和它匹配,匹配函数是开根,要求平均情况下情况和最优偏差不超过4%
(即 ∑ ( a i − b i \sum \sqrt{(a_i-b_i} ∑(ai−bi)
n ≤ 1 0 5 n\leq 10^5 n≤105
【思路】
赛时模拟退火了半天,-23都没过。
根号的凹凸性和常见函数相反,所以这题很难不通过匹配去求最优解。
主要要强调的是 sort,直接 sort 其实是一个很糟糕的举动,因为 sqrt 的导函数随着 x x x的增大值越来越小,而你 sort 后很可能是层次不齐的情况,其实反而增大了函数和。
举一个例子:{1,2,3}, {0,1,2},(1,1), (2,2), (0, 3) 会比 sort 的结果好很多。
在题目保证数据随机的情况下,本地可以模拟实际数据并测试正确率。
由于 KM 的复杂度是 O ( N 3 ) O(N^3 ) O(N3),可以选择小数据 KM 大数据贪心,以最优化平均偏差。
一个可行的贪心做法:从小到大枚举 d d d,每次贪心去看是否存在两数差 = 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;
}