In mathematics you don't understand things, you just get used to them.

分类 笔记 下的文章

Prob. 1

Desc. & Link.

行走的形式是比较自由的,因为只要走到了最优答案处就可以不管了,所以不需要考虑游戏的结束。

考虑二分答案。

然后预处理出每个节点到 $s$(另一棵树就是 $t$)的距离。

判断答案合法性:

首先枚举 $A$ 树的每个节点(节点编号小于当前二分的答案),然后判断需要构成答案的 $B$ 树上的节点距离 $t$ 的距离的奇偶性是否一致即可。

但是这样有一个问题:我们如何确保这个答案是整个一轮移动过程中最大的?

仔细考虑一下,我们判断成功的依据是行走过程中所有和不超过我们当前二分的值,那么转为判断这个问题(意思就是前面降智了)。

因为这是一棵树,所以该走的路径一定会走。

因为我们枚举了 $A$ 树中的节点,所以每次从哪两个节点走到 $s$、$t$ 是固定下来的。

草,直接 bfs 判断找可达最小值就行了。

$\Theta(n\log_{2}^{2}n)$,我觉得不行,先写。

草卡卡常过了。

#pragma GCC optimize(1)
#pragma GCC optimize(2)
#pragma GCC optimize(3)
#pragma GCC optimize(4)
#pragma GCC optimize("Ofast")
#pragma GCC optimize("inline")
#pragma GCC optimize("-fgcse")
#pragma GCC optimize("-fgcse-lm")
#pragma GCC optimize("-fipa-sra")
#pragma GCC optimize("-ftree-pre")
#pragma GCC optimize("-ftree-vrp")
#pragma GCC optimize("-fpeephole2")
#pragma GCC optimize("-ffast-math")
#pragma GCC optimize("-fsched-spec")
#pragma GCC optimize("unroll-loops")
#pragma GCC optimize("-falign-jumps")
#pragma GCC optimize("-falign-loops")
#pragma GCC optimize("-falign-labels")
#pragma GCC optimize("-fdevirtualize")
#pragma GCC optimize("-fcaller-saves")
#pragma GCC optimize("-fcrossjumping")
#pragma GCC optimize("-fthread-jumps")
#pragma GCC optimize("-funroll-loops")
#pragma GCC optimize("-fwhole-program")
#pragma GCC optimize("-freorder-blocks")
#pragma GCC optimize("-fschedule-insns")
#pragma GCC optimize("inline-functions")
#pragma GCC optimize("-ftree-tail-merge")
#pragma GCC optimize("-fschedule-insns2")
#pragma GCC optimize("-fstrict-aliasing")
#pragma GCC optimize("-fstrict-overflow")
#pragma GCC optimize("-falign-functions")
#pragma GCC optimize("-fcse-skip-blocks")
#pragma GCC optimize("-fcse-follow-jumps")
#pragma GCC optimize("-fsched-interblock")
#pragma GCC optimize("-fpartial-inlining")
#pragma GCC optimize("no-stack-protector")
#pragma GCC optimize("-freorder-functions")
#pragma GCC optimize("-findirect-inlining")
#pragma GCC optimize("-fhoist-adjacent-loads")
#pragma GCC optimize("-frerun-cse-after-loop")
#pragma GCC optimize("inline-small-functions")
#pragma GCC optimize("-finline-small-functions")
#pragma GCC optimize("-ftree-switch-conversion")
#pragma GCC optimize("-foptimize-sibling-calls")
#pragma GCC optimize("-fexpensive-optimizations")
#pragma GCC optimize("-funsafe-loop-optimizations")
#pragma GCC optimize("inline-functions-called-once")
#pragma GCC optimize("-fdelete-null-pointer-checks")
#include <cstdio>
#include <queue>

using namespace std;

const int MAXN = 1e6 + 5;

namespace IO{
    const int sz=1<<22;
    char a[sz+5],b[sz+5],*p1=a,*p2=a,*t=b,p[105];
    inline char gc(){
        return p1==p2?(p2=(p1=a)+fread(a,1,sz,stdin),p1==p2?EOF:*p1++):*p1++;
    }
    template<class T> void gi(T& x){
        x=0; char c=gc();
        for(;c<'0'||c>'9';c=gc());
        for(;c>='0'&&c<='9';c=gc())
            x=x*10+(c-'0');
    }
    inline void flush(){fwrite(b,1,t-b,stdout),t=b; }
    inline void pc(char x){*t++=x; if(t-b==sz) flush(); }
    template<class T> void pi(T x,char c='\n'){
        if(x==0) pc('0'); int t=0;
        for(;x;x/=10) p[++t]=x%10+'0';
        for(;t;--t) pc(p[t]); pc(c);
    }
    struct F{~F(){flush();}}f; 
}
using IO::gi;
using IO::pi;

template<typename _T> _T MIN ( const _T x, const _T y ) { return x < y ? x : y; }

struct GraphSet {
    int to, nx;
    GraphSet ( int T = 0, int N = 0 ) { to = T, nx = N; }
} asA[MAXN * 2], asB[MAXN * 2];

int n, s, t, cntA, cntB, beginA[MAXN], beginB[MAXN], disA[MAXN], disB[MAXN], visA[MAXN], visB[MAXN];

void makeEdgeA ( const int u, const int v ) { asA[++ cntA] = GraphSet ( v, beginA[u] ), beginA[u] = cntA; }
void makeEdgeB ( const int u, const int v ) { asB[++ cntB] = GraphSet ( v, beginB[u] ), beginB[u] = cntB; }

void dfsA ( const int u, const int lst, const int dis ) {
    disA[u] = dis;
    for ( int i = beginA[u]; i; i = asA[i].nx ) {
        int v = asA[i].to;
        if ( v == lst )    continue;
        dfsA ( v, u, dis + 1 );
    }
}

void dfsB ( const int u, const int lst, const int dis ) {
    disB[u] = dis;
    for ( int i = beginB[u]; i; i = asB[i].nx ) {
        int v = asB[i].to;
        if ( v == lst )    continue;
        dfsB ( v, u, dis + 1 );
    }
}

void behaveOneSide ( int ark, int& mnA, int& mnB, int& ord, priority_queue<int, vector<int>, greater<int>>& align ) {
    int preSave = mnA;
    while ( ! align.empty () ) {
        int u = align.top ();
        if ( u + mnB > ark )    break;
        else    align.pop ();
        for ( int i = beginA[u]; i; i = asA[i].nx ) {
            int v = asA[i].to;
            if ( visA[v] )    continue;
            visA[v] = 1, align.push ( v );
            mnA = MIN ( mnA, v );
        }
    }
    if ( mnA == preSave )    ++ ord;
    else    ord = 0;
}

void behaveAnotherSide ( int ark, int& mnA, int& mnB, int& ord, priority_queue<int, vector<int>, greater<int>>& align ) {
    int preSave = mnB;
    while ( ! align.empty () ) {
        int u = align.top ();
        if ( u + mnA > ark )    break;
        else    align.pop ();
        for ( int i = beginB[u]; i; i = asB[i].nx ) {
            int v = asB[i].to;
            if ( visB[v] )    continue;
            visB[v] = 1, align.push ( v );
            mnB = MIN ( mnB, v );
        }
    }
    if ( mnB == preSave )    ++ ord;
    else    ord = 0;
}

priority_queue<int, vector<int>, greater<int>> oneQ, anotherQ;
bool check ( const int x ) {
    for ( int i = 1; i <= n; ++ i )    visA[i] = visB[i] = 0;
    for ( ; ! oneQ.empty (); oneQ.pop () ) ;
    for ( ; ! anotherQ.empty (); anotherQ.pop () ) ;
    oneQ.push ( s ), anotherQ.push ( t );
    visA[s] = 1, visB[t] = 1;
    int turn = 0, mnA = s, mnB = t, ord = 0;
    while ( mnA > 1 || mnB > 1 ) {
        turn ^= 1;
        if ( turn )    behaveOneSide ( x, mnA, mnB, ord, oneQ );
        else    behaveAnotherSide ( x, mnA, mnB, ord, anotherQ );
        if ( ord > 2 )    break;
    }
    if ( mnA == 1 && mnB == 1 )    return 1;
    else    return 0;
}

int solve ( int l, int r ) {
    while ( l + 1 < r ) {
        int mid = ( l + r ) >> 1;
        if ( check ( mid ) )    r = mid;
        else    l = mid;
    }
    return r;
}

int main () {
    int tCase;
    gi ( tCase );
    while ( tCase -- > 0 ) {
        gi ( n ), cntA = cntB = 0;
        for ( int i = 1; i <= n; ++ i )    beginA[i] = 0, beginB[i] = 0;
        for ( int i = 1, u, v; i < n; ++ i ) {
            gi ( u ), gi ( v );
            makeEdgeA ( u, v ), makeEdgeA ( v, u );
        }
        for ( int i = 1, u, v; i < n; ++ i ) {
            gi ( u ), gi ( v );
            makeEdgeB ( u, v ), makeEdgeB ( v, u );
        }
        gi ( s ), gi ( t );
        // dfsA ( s, 0, 0 ), dfsB ( t, 0, 0 );
        pi ( solve ( 1, n << 1 ) );
    }
    return 0;
}

Prob. 2

Desc. & Link.

$n$ 很小,$q$ 也很小。

感觉这个 $n$ 不是 $2^{n}$ 的算法也不是多项式算法欸。

但复杂度一定与 $n$ 有关……

草这玩意儿折半是不是可以折半搜索?

我们可以搜出两边我们可以凑出的价格,分别记为 $A_{i},i\in[1,C_{A}]$、$B_{i},i\in[1,C_{B}]$。

然后让 $A,B$ sorted。

然后枚举 $A_{i}$,找到 $B$ 中最大的能与 $A_{i}$ 相加小于等于需要的值,然后算下贡献即可(bullshit)。

不是为什么用快读本地过数据提交瓦爆啊。。。

#include<cstdio>
#include<algorithm>
using namespace std;
void read(long long &hhh)
{
    long long x=0;
    char c=getchar();
    while(((c<'0')|(c>'9'))&(c^'-'))    c=getchar();
    if(c^'-')    x=c^'0';
    char d=getchar();
    while((d>='0')&(d<='9'))
    {
        x=(x<<3)+(x<<1)+(d^'0');
        d=getchar();
    }
    if(c^'-')    hhh=x;
    else    hhh=-x;
}
void writing(long long x)
{
    if(!x)    return;
    if(x>9)    writing(x/10);
    putchar((x%10)^'0');
}
void write(long long x)
{
    if(x<0)
    {
        putchar('-');
        x=-x;
    }
    else if(!x)
    {
        putchar('0');
        putchar('\n');
        return;
    }
    writing(x);
    putchar('\n');
}
long long n,q,endone,beginano,onesiz,onebuc[2000005],anosiz,anobuc[2000005],opl,opr,cud[45];
void dfs(long long now,long long cur)
{
    if(now==endone+1)    onebuc[++onesiz]=cur;
    else
    {
        dfs(now+1,cur+cud[now]);
        dfs(now+1,cur);
    }
}
void exdfs(long long now,long long cur)
{
    if(now==n+1)    anobuc[++anosiz]=cur;
    else
    {
        exdfs(now+1,cur+cud[now]);
        exdfs(now+1,cur);
    }
}
long long solve(long long mos)
{
    long long now=anosiz;
    long long res=0;
    for(long long i=1;i<=onesiz;++i)
    {
        while(now&&onebuc[i]+anobuc[now]>mos)    now--;
        res+=now;
    }
    return res;
}
int main()
{
//    read(n);
//    read(q);
    scanf("%lld%lld",&n,&q);
//    for(long long i=1;i<=n;++i)    read(cud[i]);
    for(long long i=1;i<=n;++i)    scanf("%lld",&cud[i]);
    endone=(n>>1);
    beginano=endone+1;
    dfs(1,0);
    exdfs(beginano,0);
    sort(onebuc+1,onebuc+onesiz+1);
    sort(anobuc+1,anobuc+anosiz+1);
    while(q--)
    {
        scanf("%lld%lld",&opl,&opr);
//        read(opl);
//        read(opr);
//        write(solve(opr)-solve(opl-1));
        printf("%lld\n",solve(opr)-solve(opl-1));
    }
    return 0;
}

Prob. 4

Desc. & Link.

相当于在树上对于每一个点找出找出一条以其为链顶的链。

设 $f_{u}$ 为 $u$ 的答案。

$$ f_{u}=\max_{v\in\text{son}(u)}\{f_{v}+(a_{u}-\text{dis}(u,v))\times b_{v},0\} $$

有乘法,然后题目中一堆常数。

:-) 斜率优化

我们令 $s_{u}=\text{dis}(1,u)$,然后

$$ \begin{aligned} f_{u} &=\max_{v\in\text{son}(u)}\{f_{v}+(a_{u}+s_{u}-s_{v})\times b_{v},0\} \\ &=\max_{v\in\text{son}(u)}\{(a_{u}-s_{u})\times b_{v}+f_{v}-s_{v}\times b_{v},0\} \end{aligned} $$

令 $y=f_{u},x=a_{u}-s_{u},k=b_{v},b=f_{v}-s_{v}\times b_{v}$,那么这个东西就是一个 $y=kx+b$。

那么我们现在需要在子树里维护凸包,并且能够支持合并凸包和插入直线。

Prob. 1

Desc. & Link.

暴力为 $\Theta(NK)$。

正解(也许):

把每一个全为正整数的子段找出来。

然后判断一下中间连接的情况即可。

但是这样决策情况太多了。

我们需要考虑贪心。

把所有整数段的个数记为 $totP$,每个子段的区间记为 $[posL_{i},posR_{i}]$,区间和记为 $sumP_{i}$

把其他的负数段个数记为 $totN$,区间和记为 $sumN_{i}$。

当 $totP\le k$ 答案显然。

我们需要考虑的是 $totP>k$ 的情况。

我们把整数段、负数段缩成点。

然后问题还是最多选 $k$ 段的最大子段和。

不过我们的序列有个性质:相邻数的正负性不同。(gu)

好了放弃以上想法。

模拟 $k$ 轮找全局最大子段和,找到一次把子段乘上 $-1$。

#include <cstdio>

typedef long long LL;

const int MAXN = 1e5 + 5;

int rint () {
    int x = 0, f = 1; char c = getchar ();
    for ( ; c < '0' || c > '9'; c = getchar () )    f = c == '-' ? -1 : f;
    for ( ; c >= '0' && c <= '9'; c = getchar () )    x = ( x << 3 ) + ( x << 1 ) + ( c & 15 );
    return x * f;
}

template<typename _T>
void wint ( _T x ) {
    if ( x < 0 )    putchar ( '-' ), x = ~ x + 1;
    if ( x > 9 )    wint ( x / 10 );
    putchar ( x % 10 ^ '0' );
}

template<typename _T> _T MAX ( const _T x, const _T y ) { return x < y ? y : x; }
template<typename _T> void swapp ( _T &x, _T &y ) { _T w = x; x = y; y = w; }

struct nodeS {
    LL val, dat, p, s;
    int l, r, pl, pr, sl, sr;
    nodeS ( LL V = 0, LL D = 0, LL P = 0, LL S = 0,
            int L = 0, int R = 0, int Pl = 0, int Pr = 0, int Sl = 0, int Sr = 0 ) {
                val = V, dat = D, p = P, s = S, l = L, r = R, pl = Pl, pr = Pr, sl = Sl, sr = Sr; }
} nodes[MAXN * 4][2];

int n, k, a[MAXN];
bool tag[MAXN * 4];

nodeS Merge ( const nodeS lch, const nodeS rch ) {
    nodeS ret;
    ret.val = lch.val + rch.val;
    ret.p = MAX ( lch.p, lch.val + rch.p );
    if ( ret.p == lch.p )    ret.pl = lch.pl, ret.pr = lch.pr;
    else    ret.pl = lch.pl, ret.pr = rch.pr;
    ret.s = MAX ( rch.s, rch.val + lch.s );
    if ( ret.s == rch.s )    ret.sl = rch.sl, ret.sr = rch.sr;
    else    ret.sl = lch.sl, ret.sr = rch.sr;
    ret.dat = MAX ( lch.s + rch.p, MAX ( lch.dat, rch.dat ) );
    if ( ret.dat == lch.dat )    ret.l = lch.l, ret.r = lch.r;
    else if ( ret.dat == rch.dat )    ret.l = rch.l, ret.r = rch.r;
    else    ret.l = lch.sl, ret.r = rch.pr;
    return ret;
}

void Upt ( const int x ) {
    nodes[x][0] = Merge ( nodes[x << 1][0], nodes[x << 1 | 1][0] );
    nodes[x][1] = Merge ( nodes[x << 1][1], nodes[x << 1 | 1][1] );
}

void Spr ( const int x ) {
    if ( ! tag[x] )    return;
    swapp ( nodes[x << 1][0], nodes[x << 1][1] );
    swapp ( nodes[x << 1 | 1][0], nodes[x << 1 | 1][1] );
    tag[x << 1] ^= 1, tag[x << 1 | 1] ^= 1, tag[x] = 0;
}

void Build ( const int x, const int l, const int r ) {
    if ( l == r ) {
        nodes[x][0] = nodeS ( a[l], a[l], a[l], a[l], l, l, l, l, l, l );
        nodes[x][1] = nodeS ( -a[l], -a[l], -a[l], -a[l], l, l, l, l, l, l );
        return;
    }
    int mid = ( l + r ) >> 1;
    Build ( x << 1, l, mid );
    Build ( x << 1 | 1, mid + 1, r );
    Upt ( x );
}

void Modify ( const int x, const int l, const int r, const int segL, const int segR ) {
    if ( l > segR || r < segL )    return;
    if ( l >= segL && r <= segR ) {
        swapp ( nodes[x][0], nodes[x][1] );
        tag[x] ^= 1;
        return;
    }
    int mid = ( l + r ) >> 1;
    Spr ( x );
    Modify ( x << 1, l, mid, segL, segR );
    Modify ( x << 1 | 1, mid + 1, r, segL, segR );
    Upt ( x );
}

int main () {
    n = rint (), k = rint ();
    for ( int i = 1; i <= n; ++ i )    a[i] = rint ();
    Build ( 1, 1, n );
    LL ans = 0;
    while ( k -- > 0 ) {
        nodeS ret = nodes[1][0];
        if ( ret.dat < 0 )    break;
        Modify ( 1, 1, n, ret.l, ret.r );
        ans += ret.dat;
    }
    wint ( ans ), putchar ( '\n' );
    return 0;
}

Prob. 2

Desc. & Link.

设 $f_{i,0/1}$ 表示把 $a_{i}$ 往头/尾放可以得到的最多的上升子序列。

$$ f_{i,0}=\begin{cases}\max\{f_{j,0},f_{j,1}\}+1,a_{i}<a_{j} \\\max\{f_{j,0},f_{j,1}\},a_{i}\ge a_{j}\end{cases} \\f_{i,1}=\begin{cases}\max\{f_{j,0},f_{j,1}\},a_{i}<a_{j} \\\max\{f_{j,0},f_{j,1}\}+1,a_{i}\ge a_{j}\end{cases} $$

不行。

考虑普通的 LIS 怎么做。

$$ f_{i}=\max\{f_{j}\}+1,a_{i}>a_{j} $$

$$ a_{i}<a_{j},i<j $$

选择往前放的元素,放得越晚越靠前。

选择往后放的元素,放得越晚越靠后。

那么需要做的是把相对较大的元素往后,相对较小的元素往前。

连边,把李三花后的 $a_{i}$ 连向 $\text{trans}(i,0),\text{trans}(i,1),\text{trans}(x,0/1)=n-x+1/x+n$。

#include <cstdio>
#include <algorithm>

using namespace std;

const int MAXN = 2e5 + 5;

int rint () {
    int x = 0, f = 1; char c = getchar ();
    for ( ; c < '0' || c > '9'; c = getchar () )    f = c == '-' ? -1 : f;
    for ( ; c >= '0' && c <= '9'; c = getchar () )    x = ( x << 3 ) + ( x << 1 ) + ( c & 15 );
    return x * f;
}

template<typename _T>
void wint ( _T x ) {
    if ( x < 0 )    putchar ( '-' ), x = ~ x + 1;
    if ( x > 9 )    wint ( x / 10 );
    putchar ( x % 10 ^ '0' );
}

template<typename _T> _T MAX ( const _T x, const _T y ) { return x < y ? y : x; }

struct Value {
    int val, pos;
    Value ( int V = 0, int P = 0 ) { val = V, pos = P; }
    bool operator < ( const Value &another ) { return val < another.val; }
} vals[MAXN];

struct GraphSet {
    int to, nx;
    GraphSet ( int T = 0, int N = 0 ) { to = T, nx = N; }
} as[MAXN * 2];

int n, cnt, len, degin[MAXN], a[MAXN], b[MAXN], buc[MAXN], sywf[MAXN];

void makeEdge ( const int u, const int v ) { as[++ cnt] = GraphSet ( v, degin[u] ), degin[u] = cnt; }
int Trans ( const int x, const int y ) { return ! y ? n - x + 1 : x + n; }

void ADD ( int p, const int x ) { for ( ; p <= ( n << 1 ); p += p & -p )    sywf[p] = MAX ( sywf[p], x ); }
int ASK ( int p ) { int res = 0; for ( ; p; p -= p & -p )    res = MAX ( res, sywf[p] ); return res; }
int CMP ( const int x, const int y ) { return x > y; }

int main () {
//    freopen ( "dequexlis.in", "r", stdin );
//    freopen ( "dequexlis.out", "w", stdout );
    n = rint ();
    for ( int i = 1; i <= n; ++ i )    a[i] = b[i] = rint ();
    sort ( b + 1, b + 1 + n );
    len = unique ( b + 1, b + 1 + n ) - b - 1;
    for ( int i = 1; i <= n; ++ i )    a[i] = lower_bound ( b + 1, b + 1 + len, a[i] ) - b;
    for ( int i = 1; i <= n; ++ i )    vals[i] = Value ( a[i], i );
    for ( int i = 1; i <= n; ++ i ) {
        makeEdge ( a[i], Trans ( i, 0 ) );
        makeEdge ( a[i], Trans ( i, 1 ) );
    }
    int BUC = 0;
    for ( int x_x = 1; x_x <= n; ++ x_x ) {
        int u = x_x;
        BUC = 0;
        for ( int i = degin[u]; i; i = as[i].nx ) {
            int v = as[i].to;
            buc[++ BUC] = v;
        }
        sort ( buc + 1, buc + 1 + BUC, CMP );
        for ( int i = 1; i <= BUC; ++ i )    ADD ( buc[i], 1 + ASK ( buc[i] - 1 ) );
    }
    wint ( ASK ( n << 1 ) ), putchar ( '\n' );
    return 0;
}

/* Jesus bless all */

Prob. 3

放弃人生打了个 50 的记搜。

#include <cstdio>
#include <map>
#define mod ( 998244853 )

using namespace std;

template<typename _T> _T MAX ( const _T x, const _T y ) { return x < y ? y : x; }

int n, m;

namespace Course {
const int MAXN = 255;
int f[MAXN][MAXN][MAXN];
bool vis[MAXN][MAXN][MAXN];

int dfs ( const int mx, const int a, const int b ) {
    if ( vis[a][b][mx] )    return f[a][b][mx];
    vis[a][b][mx] = 1;
    if ( a == n && b == m )    return f[a][b][mx] = mx;
    if ( a < n )    f[a][b][mx] = ( f[a][b][mx] + dfs ( MAX ( mx, a - b + 1 ), a + 1, b ) ) % mod;
    if ( b < m )    f[a][b][mx] = ( f[a][b][mx] + dfs ( mx, a, b + 1 ) ) % mod;
    return f[a][b][mx];
}
}

int main () {
    // freopen ( "maxpsum.in", "r", stdin );
    // freopen ( "maxpsum.out", "w", stdout );
    scanf ( "%d%d", &n, &m );
    if ( n <= 250 && m <= 250 )    printf ( "%d\n", Course :: dfs ( 0, 0, 0 ) );
    // else    printf ( "%d\n", Might :: dfs ( 0, 0, 0 ) );
    return 0;
}

Prob. 4

Desc. & Link.

Problem. 1 Junior - Thinking

Desc. & Link.

注意到值域乘范围刚好能过。

然后就存两个桶即可。。。(数组开小飞了半天才调出来。。。)

Problem. 2 Junior / Senior - Thinking

Desc. & Link.

考虑一次反转后对整个序列造成的影响。

每次操作相当于把整个序列分成了 $2^{n-q}$ 个块,我们只需要考虑块内和块外。

考虑一个块对其他块的情况。

嗯。

没有影响,完。

那么我们只需要考虑如何快速计算出每个块内的变化即可。

像归并排序一样考虑问题,把序列分成 $n$ 层(把二叉树画出来)。

要反转区间 $[l,r]$ 就要翻转 $[l,m],[m+1,r],m=\lfloor\frac{l+r}{2}\rfloor$,以此类推。

然后就预处理出每一层顺序对逆序对即可。

Problem. 3 Junior / Senior - Thinking

首先否决往大了想。

我们首先可以通过背包算出所有能够组成的值。

然后不会了。

Description

Link.

定义一条链的价值为链上点权乘积除以节链上点数,求一条价值最小的链。

Solution

结论:答案链上最多包含一个 $2$(其余全为 $1$),并且不在链的两端点。

证明:我们问题分成两个 $\texttt{pass}$。

  • $\texttt{pass 1}$:$\forall u,s.t.x_{u}\ge2$。

答案显然为 $\min\{x_{u}\},u\in V$。

  • $\texttt{pass 2}$:$\exists E'\subset E,s.t.x_{u}=1,u\in E'\wedge x_{v}\ge2,v\in E\setminus E'$。
    • 我们设我们选出的链为大概这样的造型:

$$ 1\rightarrow1\rightarrow\cdots\rightarrow X\rightarrow1\rightarrow1\cdots $$

即一堆 $1$ 中夹了一个 $X$。

我们设 $X$ 左边有 $l$ 个节点,右边有 $r$ 个节点。

则价值为整条链 $\frac{X}{l+r+1}$,左边 $\frac{1}{l}$,右边 $\frac{1}{r}$。

为方便我们这里设 $l<r$。

那么左边的价值一定大于右边。

这里假设 $\frac{1}{r}>\frac{X}{l+r+1}$,则有 $X<\frac{l+1}{r}+1$,又 $r\ge l+1$,所以 $\frac{l+1}{r}\le1$。(反之可以证伪,懒得写了 QwQ)

所以有 $X\le2$。

又 $X\neq1$,所以 $X=2$。

    • 我们设我们选出的链为大概这样的造型:

$$ 1\rightarrow1\rightarrow\cdots\rightarrow X\rightarrow1\rightarrow\cdots\rightarrow1\rightarrow Y\rightarrow1\cdots $$

即一堆 $1$ 中夹了一个 $X$ 一个 $Y$。

这里我们可以把 $Y$ 以前当成 $\texttt{pass 2}$ 的第一个类型,设其共有 $N$ 个数。

那么假设我们加入 $Y$ 更优,即有 $\frac{XY}{N+1}<\frac{X}{N}$,则有 $NY<N+1$,由于 $Y\neq1$,所以加入 $Y$ 是更劣的。

后面的同理就可以推广了。

于是得证 QwQ。

然后我们就可以 DP 了。

设 $f_{u,0/1}$ 表示节点 $u$ 权值为的情况下最优答案。

转移就分类讨论一下:

  • $x_{u}=1$

$$ \begin{cases} f_{u,0}=\max\{f_{v,0}\}+1 \\ f_{u,1}=\max\{f_{v,1}\}+1 \end{cases} $$

  • $x_{u}=2$

$$ f_{u,1}=\max\{f_{v,0}\}+1 $$

答案也需要分类讨论(这里设 $x,y\in\text{son}(u)$):

  • $x_{u}=1$

答案为 $\frac{1}{\max\{f_{x,0}+f_{y,0}+1\}}$,以及 $\frac{2}{\max\{f_{x,0}+f_{y,1}\}+1}$。

  • $x_{u}=2$

答案为 $\frac{2}{\max\{f_{x,0}+f_{y,0}+1\}}$。

用四个变量维护最大、次大的 $f_{0},f_{1}$ 即可。

#include <cstdio>

const int MAXN = 1e6 + 5;

int rint () {
    int x = 0, f = 1; char c = getchar ();
    for ( ; c < '0' || c > '9'; c = getchar () )    f = c == '-' ? -f : f;
    for ( ; c >= '0' && c <= '9'; c = getchar () )    x = ( x << 3 ) + ( x << 1 ) + ( c & 15 );
    return x * f;
}

template<typename _T>
void wint ( _T x ) {
    if ( x < 0 )    putchar ( '-' ), x = ~ x + 1;
    if ( x > 9 )    wint ( x / 10 );
    putchar ( x % 10 ^ '0' );
}

template<typename _T> _T MIN ( const _T x, const _T y ) { return x > y ? y : x; }

struct starS {
    int to, nx;
    starS ( int T = 0, int N = 0 ) { to = T, nx = N; }
} as[MAXN * 2];

int n, cnt, Up = 1e9, Dn = 1, mnMg = 1e9, a[MAXN], f[MAXN][2], bgin[MAXN];

void pushEdge ( const int u, const int v ) { as[++ cnt] = starS ( v, bgin[u] ); bgin[u] = cnt; }

void checkUpt ( const int x, const int y ) { if ( Up * y > Dn * x )    Up = x, Dn = y; }

void dfs ( const int u, const int lst ) {
    int mx0 = 0, se0 = 0, mx1 = 0, se1 = 0;
    for ( int i = bgin[u]; i; i = as[i].nx ) {
        int v = as[i].to;
        if ( v == lst )    continue;
        dfs ( v, u );
        if ( f[v][0] > f[mx0][0] )    se0 = mx0, mx0 = v;
        else if ( f[v][0] > f[se0][0] )    se0 = v;
        if ( f[v][1] > f[mx1][1] )    se1 = mx1, mx1 = v;
        else if ( f[v][1] > f[se1][1] )    se1 = v;
    }
    if ( a[u] == 1 ) {
        f[u][0] = f[mx0][0] + 1;
        checkUpt ( 1, f[mx0][0] + f[se0][0] + 1 );
        if ( ! mx1 )    return;
        f[u][1] = f[mx1][1] + 1;
        if ( mx0 != mx1 )    checkUpt ( 2, f[mx0][0] + f[mx1][1] + 1 );
        else {
            checkUpt ( 2, f[se0][0] + f[mx1][1] + 1 );
            if ( se1 )    checkUpt ( 2, f[mx0][0] + f[se1][1] + 1 );
        }
    }
    else if ( a[u] == 2 )    f[u][1] = f[mx0][0] + 1, checkUpt ( 2, f[mx0][0] + f[se0][0] + 1 );
}

int main () {
    n = rint ();
    for ( int i = 1, u, v; i < n; ++ i ) {
        u = rint (), v = rint ();
        pushEdge ( u, v ), pushEdge ( v, u );
    }
    for ( int i = 1; i <= n; ++ i )    a[i] = rint (), mnMg = MIN ( mnMg, a[i] );
    if ( mnMg > 1 )    wint ( mnMg ), putchar ( '/' ), wint ( 1 ), putchar ( '\n' );
    else    dfs ( 1, 0 ), wint ( Up ), putchar ( '/' ), wint ( Dn ), putchar ( '\n' );
    return 0;
}

Description

Link.

求满足

$$\prod _{i=2}^{k} \binom{a_{b_{i-1}}}{a_{b_i}} \mod 2 = \binom{a_{b_1}}{a_{b_2}} \times \binom{a_{b_2}}{a_{b_3}} \times \cdots \binom{a_{b_{k-1}}}{a_{b_k}} \mod 2 > 0$$

的子序列个数。

Solution

哇哦。

$$ \begin{aligned} &\ \ \ \ \prod_{i=2}^{k}{a_{b_{i}-1}\choose a_{b_{i}}} \\ &\equiv\prod_{i=2}^{k}{\lfloor\frac{a_{b_{i}-1}}{2}\rfloor\choose\lfloor\frac{a_{b_{i}}}{2}\rfloor}\times{a_{b_{i}-1}\bmod2\choose a_{b_{i}}\bmod2} \end{aligned} (\operatorname{mod} 2) $$

式子后面的 $\dbinom{a_{b_{i}-1}\bmod2}{a_{b_{i}\bmod2}}$ 一共有四种情况,其中只有 $\dbinom{0}{1}=0$。其他都为 $1$。

意味着只要出现 $a_{b_{i}-1}\equiv0\bmod2$ 且 $a_{b_{i}}\equiv1\bmod1$ 的情况,整个式子就为零了。

结论:$\dbinom{n}{m}\equiv0\space(\operatorname{mod}2)$ 当且仅当 $n\operatorname{bitand}m=m$。

证明(也许不是特别严谨):我们可以知道:

$$ {n\choose m}={\lfloor\frac{n}{2}\rfloor\choose\lfloor\frac{m}{2}\rfloor}\times{n\bmod 2\choose m\bmod2}={\lfloor\frac{\lfloor\frac{n}{2}\rfloor}{2}\rfloor\choose\lfloor\frac{\lfloor\frac{m}{2}\rfloor}{2}\rfloor}\times {\lfloor\frac{n}{2}\rfloor\bmod2\choose\lfloor\frac{m}{2}\rfloor\bmod2}\times{n\bmod 2\choose m\bmod2}=\cdots $$

我们发现:

$$ {\lfloor\frac{\lfloor\frac{\lfloor\frac{n}{2}\rfloor}{2}\rfloor}{\cdots}\rfloor\choose\lfloor\frac{\lfloor\frac{\lfloor\frac{m}{2}\rfloor}{2}\rfloor}{\cdots}\rfloor} $$

这一坨,就是在一直进行二进制移位,$\operatorname{shr}1$。

那么我们可以得出一个结论:如果对于我们记 $(n)_{k}$ 表示 $n$ 在二进制意义下的第 $k$ 位。$(n)_{k}\in[0,1]$

那么对于 $\forall i$,有 $(n)_{i}=0$ 且 $(m)_{i}=1$,那么 $\dbinom{n}{m}\equiv0\space(\operatorname{mod} 2)$。

所以 $n\operatorname{bitand}m=m$,证毕。

我们题目要求的是最后算出来是个奇数,那么就不能存在 $a_{b_{i}-1}\operatorname{bitand}a_{b_{i}}=a_{b_{i}}$。

也就是 $a_{b_{i}}$ 为 $a_{b_{i}-1}$ 的子集。

接下来我们可以设计一个 DP,我们设 $f_{i}$ 为以 $a_{i}$ 为开头的答案。

那么转移就是加法原理:

$$ f_{i}=f_{i}+f_{j},j\in a_{i}\wedge t_{j}>i $$

其中 $t_{i}$ 表示 $i$ 在序列中的位置。

时间复杂度由二项式定理可知是 $\Theta(3^{\log_{2}\max\{a_{i}\}})$。

#include <cstdio>
#define mod ( 1000000007 )

const int MAXN = 250000 + 5;

int N;
int val[MAXN], dp[MAXN];
int buc[MAXN];

int main( ){
    scanf( "%d", &N ); for( int i = 1; i <= N; ++ i ){ scanf( "%d", &val[i] ); buc[val[i]] = i; }
    int Ans = 0;
    for( int i = N; i; -- i ){
        dp[i] = 1;
        for( int j = val[i] & ( val[i] - 1 ); j; j = ( j - 1 ) & val[i] ){
            if( buc[j] > i )    dp[i] = ( dp[i] + dp[buc[j]] ) % mod;
        }
        Ans = ( Ans + dp[i] ) % mod;
    }
    printf( "%d\n", ( Ans - N + mod ) % mod );
    return 0;
}