【多校训练】2021牛客多校第一场

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

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

A. Alice and Bob

【题意】

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

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

【思路】

首先我们可以考虑暴力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 n5000,赛时我们直接打表就过了。

事实上,可以发现,对于每个 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 n105

【思路】

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

这个可以通过线段树合并来 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 n2000

【思路】

略。

复杂度 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,m1000

【思路】

首先,旋转是任意的,那么我们其实不需要关注现在的情况,其实 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 n105

【思路】

赛时不会。

最优解性质

考虑任意一个最优解,我们把交换后的数字重新放回原来的位置,相当于为每一个元素分配了它在答案中的符号。比如 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 n5×105

【思路】

赛时数据弱,暴力可过。

考虑正解,两个数 a , b a,b a,b m m m余数相同,当且仅当 ∣ a − b ∣ |a-b| ab m m m的约数。

问题转化为找到最小的 m m m使得它不是任意一个 ∣ a i − a j ∣ |a_i-a_j| aiaj的约数,而我们只需要知道每一个差值,就可以通过枚举 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} PMXi卷积,判断对应位置是否大于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 n5000

【思路】

我们希望设计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 iln, 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+1j所有点,使得到达时间满足每个点的合法时间段(如果提前到了可以等待)。同时要支持修改相邻两点的距离,或者修改一个点的合法时间段。

n , Q ≤ 1 0 6 n,Q\leq 10^6 n,Q106

【思路】

一看就很线段树。

考虑 $(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(n1),vi=vi+di+d(i+1)++d(n1)

现在$ (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 0n1的序列,用 0 ∼ n − 1 0\sim n-1 0n1去和它匹配,匹配函数是开根,要求平均情况下情况和最优偏差不超过4%

(即 ∑ ( a i − b i \sum \sqrt{(a_i-b_i} (aibi

n ≤ 1 0 5 n\leq 10^5 n105

【思路】

赛时模拟退火了半天,-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;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值