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

标签 dp 下的文章

Description

Link.

这道题 的加强版。

Solution

题解里面大多数都是概率 DP,或者是期望 DP 然后是逆推。甚至不给 DP 的转移式。机房 yyds Reanap 发了一篇逆推的题解,那我就来补一篇正推的期望 DP 的填表法做法。

首先这道题看上去好像可以状压的样子,我们可以设 $f_{i,S}$ 表示当前打了 $i$ 次,敌方的情况是 $S$ 的期望。

不过仔细想一下发现我们只需要知道各种血量的奴隶主有多少即可。

于是我们重新设计 DP 的状态:$f_{s,i,j,k}$ 表示目前打了 $s$ 次,敌方分别有 $i$、$j$、$k$ 个 1hp、2hp、3hp 的奴隶主。

首先我们令 $T=[i+j+k<K]$

那么我们的方程就是:

$$ f_{s,i,j,k}=\begin{cases} f_{s-1,i-1,j,k}+\frac{i}{i+j+k+1},M=1\land i\neq0 \\ f_{s-1,i-1,j,k}+\frac{i}{i+j+k+1},M=2\land i\neq0 \\ f_{s-1,i+1,j-1+T,k}+\frac{j}{i+j+k+1},M=2\land j\neq0 \\ f_{s-1,i-1,j,k}+\frac{i}{i+j+k+1},M=3\land i\neq0 \\ f_{s-1,i+1,j-1,k+T}+\frac{j}{i+j+k+1},M=3\land j\neq0 \\ f_{s-1,i,j+1,k-1+T}+\frac{k}{i+j+k+1},M=3\land k\neq0 \end{cases} $$

这个方程挺好理解的,基本就等于照题意模拟。

然后我们发现转移式中的系数部分和 $f$ 数组没有关系,所以我们可以用矩阵来加速这个东西。

数一数状态数,直接加速直接 T 飞。

有一个矩阵加速常用的 trick,预处理矩阵 2 的幂。

然后取模卡卡常即可。

(代码不保证稳定能过)

#include <cstdio>
#define mod ( 998244353 )

using namespace std;
typedef long long LL;

char buf[1 << 21], *p1 = buf, *p2 = buf;
#define getchar( ) ( p1 == p2 && ( p2 = ( p1 = buf ) + fread( buf, 1, 1 << 21, stdin ), p1 == p2 ) ? EOF : *p1 ++ )

template<typename _T>
void read( _T &x )
{
    x = 0;
    char c = getchar( );
    _T f = 1;
    while( c < '0' || c > '9' )
    {
        if( c == '-' )    f = -1;
        c = getchar( );
    }
    while( c >= '0' && c <= '9' )    x = ( x << 3 ) + ( x << 1 ) + ( c ^ '0' ), c = getchar( );
    x *= f;
}

template<typename _T, typename... Args>
void read( _T &t, Args&... args ) { read( t ), read( args... ); }

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

template<typename _T>
void Add( _T &x, _T y )
{
    if( y >= mod )  y %= mod;
    x += y;
    if( x >= mod )  x -= mod;
}

template<typename _T>
_T square( _T x ) { return x * x; }

const int Maxn = 10 + 5, Maxk = 170 + 5;
int T, M, K, S, Unite[Maxn][Maxn][Maxn];
LL tmp[Maxk], Ans[Maxk], Inv[Maxn];
struct Matrix
{
    LL mat[Maxk][Maxk];

    friend Matrix operator * ( const Matrix &one, const Matrix &another )
    {
        Matrix res;
        for( int i = 0; i <= S + 1; ++ i )
        {
            for( int j = 0; j <= S + 1; ++ j )
            {
                res.mat[i][j] = 0;
                for( int k = 0; k <= S + 1; ++ k )    Add( res.mat[i][j], one.mat[i][k] * another.mat[k][j] );
            }
        }
        return res;
    }
} dp[Maxk];

template<typename _T>
_T qkpow( _T base, _T times )
{
    _T res = 1;
    while( times )
    {
        if( times & 1 )    res = ( LL )res * base % mod;
        base = ( LL )base * base % mod;
        times >>= 1;
    }
    return res;
}

void progressInversions( ) { for( int i = 0; i <= 10; ++ i )    Inv[i] = qkpow( i, mod - 2 ); }
signed main( )
{
    progressInversions( );
    read( T, M, K );
    for( int i = 0; i <= K; ++ i )
    {
        int UpI;
        if( M > 1 )    UpI = K - i;
        else    UpI = 0;
        for( int j = 0; j <= UpI; ++ j )
        {
            int UpJ;
            if( M > 2 )    UpJ = K - i - j;
            else    UpJ = 0;
            for( int k = 0; k <= UpJ; ++ k )    Unite[i][j][k] = ++ S;
        }
    }
    for( int i = 0; i <= K; ++ i )
    {
        int UpI;
        if( M > 1 )    UpI = K - i;
        else    UpI = 0;
        for( int j = 0; j <= UpI; ++ j )
        {
            int UpJ;
            if( M > 2 )    UpJ = K - i - j;
            else    UpJ = 0;
            for( int k = 0; k <= UpJ; ++ k )
            {
                int Add;
                if( i + j + k < K )    Add = 1;
                else    Add = 0;
                if( M == 1 && i )    dp[0].mat[Unite[i][j][k]][Unite[i - 1][j][k]] = ( LL )i * Inv[i + j + k + 1] % mod;
                else if( M == 2 )
                {
                    if( i )    dp[0].mat[Unite[i][j][k]][Unite[i - 1][j][k]] = ( LL )i * Inv[i + j + k + 1] % mod;
                    if( j )    dp[0].mat[Unite[i][j][k]][Unite[i + 1][j - 1 + Add][k]] = ( LL )j * Inv[i + j + k + 1] % mod;
                }
                else if( M == 3 )
                {
                    if( i )    dp[0].mat[Unite[i][j][k]][Unite[i - 1][j][k]] = ( LL )i * Inv[i + j + k + 1] % mod;
                    if( j )    dp[0].mat[Unite[i][j][k]][Unite[i + 1][j - 1][k + Add]] = ( LL )j * Inv[i + j + k + 1] % mod;
                    if( k )    dp[0].mat[Unite[i][j][k]][Unite[i][j + 1][k - 1 + Add]] = ( LL )k * Inv[i + j + k + 1] % mod;
                }
                dp[0].mat[Unite[i][j][k]][Unite[i][j][k]] = dp[0].mat[Unite[i][j][k]][S + 1] = Inv[i + j + k + 1];
            }
        }
    }
    dp[0].mat[S + 1][S + 1] = 1;
    for( int i = 1; i <= 60; ++ i )    dp[i] = square( dp[i - 1] );
    while( T -- > 0 )
    {
        LL N;
        read( N );
        for( int i = 0; i <= S + 1; ++ i )  Ans[i] = 0;
        if( M == 1 )    Ans[Unite[1][0][0]] = 1;
        else if( M == 2 )    Ans[Unite[0][1][0]] = 1;
        else    Ans[Unite[0][0][1]] = 1;
        for( int i = 0; i <= 60; ++ i )
        {
            if( ( N >> i ) & 1 )
            {
                for( int j = 0; j <= S + 1; ++ j )
                {
                    tmp[j] = 0;
                    for( int k = 0; k <= S + 1; ++ k )    Add( tmp[j], Ans[k] * dp[i].mat[k][j] );
                }
                for( int j = 0; j <= S + 1; ++ j )    Ans[j] = tmp[j];
            }
        }
        write( Ans[S + 1] ), putchar( '\n' );
    }
    return 0;
}

Description

Link.

给出 $N$ 个单词,每个单词有个非负权值 $C_{i}$,现要将它们分成连续的若干段,每段的代价为此段单词的权值和,还要加一个常数 $M$,即 $(\sum C_{i})^{2}+M$。现在想求出一种最优方案,使得总费用之和最小

Solution

设 $f_{i}$ 表示 $n=i$ 时的答案,转移方程为:

$$ f_{i}=\min\{f_{j}+S_{i..j}^{2}+M\} $$

显然过不了,推成可以斜率优化的形式。

我们假设决策 $j$ 优于决策 $k$ 且 $k<j<i$。

(一种错误的思路)

$$ f_{j}+S_{i..j}^{2}+M<f_{k}+S_{i..k}^{2}+M $$

$$ f_{j}+(S_{i}-S_{j})^{2}<f_{k}+(S_{i}-S_{k})^2 $$

$$ f_{j}+S_{i}^2-2S_{i}S_{j}+S_{j}^{2}<f_{k}+S_{i}^2-2S_{i}S_{k}+S_{k}^{2} $$

$$ f_{j}-2S_{i}S_{j}+S_{j}^{2}<f_{k}-2S_{i}S_{k}+S_{k}^{2} $$

$$ f_{j}-2S_{i}S_{j}+2S_{i}S_{k}+S_{j}^{2}-S_{k}^{2}-f_{k}<0 $$

$$ f_{j}-2S_{i}(S_{j}+ S_{k})+(S_{j}-S_{k})(S_{j}+S_{k})-f_{k}<0 $$

$$ f_{j}-(S_{j}+ S_{k})(2S_{i}+S_{j}-S_{k})-f_{k}<0 $$

$$ f_{j}-\frac{S_{j}+ S_{k}}{2S_{i}+S_{j}-S_{k}}-f_{k}<0 $$

最后发现做 $\text{TM}$ 不出来。

于是重新推:

(另一种错误思路)(我简直自闭)

$$ f_{j}+S_{i..j}^{2}+M<f_{k}+S_{i..k}^{2}+M $$

$$ f_{j}+(S_{i}-S_{j})^{2}<f_{k}+(S_{i}-S_{k})^2 $$

$$ f_{j}+S_{i}^2-2S_{i}S_{j}+S_{j}^{2}<f_{k}+S_{i}^2-2S_{i}S_{k}+S_{k}^{2} $$

$$ f_{j}-2S_{i}S_{j}+S_{j}^{2}<f_{k}-2S_{i}S_{k}+S_{k}^{2} $$

$$ f_{j}-f_{k}+S_{j}^{2}-S_{k}^{2}<2S_{i}S_{j}-2S_{i}S_{k} $$

$$ f_{j}-f_{k}+S_{j}^{2}-S_{k}^{2}<2S_{i}(S_{j}-S_{k}) $$

$$ f_{j}-f_{k}+(S_{j}-S_{k})(S_{j}+S_{k})<2S_{i}(S_{j}-S_{k}) $$

$$ \frac{f_{j}-f_{k}}{2(S_{j}-S_{k})}+\frac{S_{j}+S_{k}}{2}<S_{i} $$

嗯,还是 $\text{TM}$ 做不来。

好了,推倒重来。

(这次是对的)

$$ f_{i}=\min\{f_{j}+S_{i..j}^{2}+M\} $$

$$ f_{j}+S_{i..j}^{2}+M<f_{k}+S_{i..k}^{2}+M $$

$$ f_{j}+(S_{i}-S_{j})^{2}<f_{k}+(S_{i}-S_{k})^2 $$

$$ f_{j}+S_{i}^2-2S_{i}S_{j}+S_{j}^{2}<f_{k}+S_{i}^2-2S_{i}S_{k}+S_{k}^{2} $$

$$ f_{j}-2S_{i}S_{j}+S_{j}^{2}<f_{k}-2S_{i}S_{k}+S_{k}^{2} $$

$$ f_{j}-f_{k}+S_{j}^{2}-S_{k}^{2}<2S_{i}S_{j}-2S_{i}S_{k} $$

$$ f_{j}-f_{k}+S_{j}^{2}-S_{k}^{2}<2S_{i}(S_{j}-S_{k}) $$

$$ \frac{(f_{j}+S_{j}^{2})-(f_{k}+S_{k}^{2})}{2(S_{j}-S_{k})}<S_{i} $$

我们令 $Y_{i}=f_{i}+S_{i}^{2}$,$X_{i}=2S_{i}$,则我们有:

$$ \frac{Y_{j}-Y_{k}}{X_{j}-X_{k}}<S_{i} $$

挺好的,正好就是斜率优化的形式!

斜率优化!

牛逼啊啊哎哎啊啊哎哎啊啊哎哎啊啊哎哎啊啊哎哎啊啊哎哎啊啊哎哎啊啊哎哎啊啊哎哎啊啊哎哎啊啊哎哎啊啊哎哎啊啊哎哎啊啊哎哎啊啊哎哎啊啊哎哎啊啊哎哎啊啊哎哎啊啊哎。

那么接下来我们设 $F(j,k)=\frac{Y_{j}-Y_{k}}{X_{j}-X_{k}}$。

其中如果满足 $F(j,k)<S_{i}$,则我们称决策 $j$ 优于决策 $k$。

现在我们继续设 $k<j<i$,那么如果满足 $F(i,j)<F(j,k)$,决策 $j$ 就永远不可能成为最优解。

证明不会,$\text{PPT}$ 没看懂。

最后放个 $\text{PPT}$ 里写的 $\text{Summary}$:

1、用一个单调队列来维护解集。

2、假设队列中从头到尾已经有元素 $a$,$b$,$c$。那么当 $d$ 要入队的时候,维护队列的上凸性质,即如果 $F(d,c)<F(c,b)$,那么删除点 $c$。直到找到 $F(d,x)\ge F(x,y)$ 为止,并将 $d$ 点加入在该位置中。

3、求解的时候,从对头开始,如果已有元素 $a$,$b$,$c$,当 $i$ 点要求解时,如果 $F(b,a)<S_{i}$,说明 $b$ 点比 $a$ 点更优,$a$ 点直接排除,于是 $a$ 出队,最后 $f_{i} = \mathrm{getDp}(Q[\mathrm{head}])$。

#include <cstdio>
#include <cstdlib>
using namespace std;
typedef long long LL;

namespace MySpace
{
    template<class _T>
    void read(_T &x)
    {
        x = 0;
        char c = getchar();
        _T f = 1;
        while( c < '0' || c > '9' )
        {
            if( c == '-' )    f = -1;
            if( c == -1 )    exit( 0 );
            c = getchar();
        }
        while( c >= '0' && c <= '9' )    x = x * 10 + c - '0', c = getchar();
        x *= f;
    }
    
    template<class _T>
    void write(_T x)
    {
        if( x < 0 )    putchar( '-' ), x = -x;
        if( x > 9 )    write( x / 10 );
        putchar( x % 10 + '0' );
    }
    
    const LL MAXN = 5e5 + 5;
    LL N, M, Dp[MAXN], Q[MAXN], S[MAXN], A[MAXN];
    
    LL Square( LL x ) { return x * x; }
    LL Up( LL i ) { return Dp[i] + Square( S[i] ); }
    LL Down( LL i ) { return S[i] << 1; }
    LL FracUp( LL j, LL k ) { return Up( j ) - Up( k ); }
    LL FracDown( LL j, LL k ) { return Down( j ) - Down( k ); }
}

int main()
{
    using namespace MySpace;
    while( ~ scanf( "%lld%lld", &N, &M ) )
    {
        for( LL i = 1; i <= N; ++ i )    scanf( "%lld", &A[i] ), S[i] = S[i - 1] + A[i];
        LL l = 1, r = 1;
        Q[r] = 0;
        for( LL i = 1; i <= N; ++ i )
        {
            while( l < r && FracUp( Q[l + 1], Q[l] ) < S[i] * FracDown( Q[l + 1], Q[l] ) )    l ++;
            Dp[i] = Dp[Q[l]] + Square( S[i] - S[Q[l]] ) + M;
            while( l < r && FracUp( i, Q[r] ) * FracDown( Q[r], Q[r - 1] ) <= FracUp( Q[r], Q[r - 1] ) * FracDown( i, Q[r] ))    r --;
            Q[++ r] = i;
        }
        printf( "%lld\n", Dp[N] );
    }
//    while( read( N ), read( M ), 1 )
//    {
//        for( LL i = 1; i <= N; ++ i )    read( A[i] ), S[i] = S[i - 1] + A[i];
//        LL l = 1, r = 0;
//        Q[l] = 0;
//        for( LL i = 1; i <= N; ++ i )
//        {
//            while( l <= r && FracUp( Q[l + 1], Q[l] ) < S[i] * FracDown( Q[l + 1], Q[l] ) )    l ++;
//            Dp[i] = Dp[Q[l]] + Square( S[i] - S[Q[l]] ) + M;
//            while( l <= r && FracUp( i, Q[r - 1] ) * FracDown( Q[r - 1], Q[r - 2] ) < FracUp( Q[r - 1], Q[r - 2] ) * FracDown( i, Q[r - 1] ))    r ++;
//            Q[++ r] = i;
//        }
//        write( Dp[N] ), putchar( '\n' );
//    }
    return 0;
}

Description

Link.

一完全图有 $n$ 个节点 $0,...,n-1$,其中边 $(i,j)$ 的权值为 $i\oplus j$,其中 $\oplus$ 为位异或操作,试求出最小生成树的边权和。

Solution

先从递推的层面考虑.

我们定义 $F(n)$ 表示结点数为 $n$ 的答案,也就是最小生成树的边权和.

首先边界条件为 $F(0)=0,F(1)=1$.

然后我们考虑如何从 $F(n-1)$ 推到 $F(n)$.

每当我们新加入一个结点 $n-1$(题目结点编号从 0 开始),它的点权为其本身,也就是 $n-1$,那么此时我们就要从之前的 $n-1$ 个结点中选出一个点与 $n-1$ 相连构成当前的最小生成树.

因为边 $(u,v)$ 的边权 $w(u,v)=u\ \mathrm{xor}\ v$ 且图为完全图,所以我们每加入一个新结点 $n-1$ 时,所有我们之前的 $0\cdots n-2$ 号结点都可以被选择.

那么问题转化为:对于一个数 $n-1$,我们需要选出一个整数 $x\in[0,n-1)$ 使得 $(n-1)\ \mathrm{xor}\ x$ 最小.

考虑异或运算的定义:每一位相同为零,不同为一.

那么我们选出的 $x$,需要满足二进制意义下每一位和 $n-1$ 尽量相同,并且从右到左(也就是二进位从低到高)的第一个不同的位置尽量低.

那么结论就摆在眼前了,我们选择的这个 $x$ 为 $(n-1)-\mathrm{lowbit}(n-1)$.

为什么?想想 $\mathrm{lowbit(x)}$ 操作的定义:二进制下 $x$ 最低的 1 和后面的 0 组成的二进制数.

这样结论的正确性就显然了.

我们 $F(n)$ 的递推公式为 $F(n)=F(n-1)+(n\ \mathrm{xor}\ (n\ \mathrm{xor}\ \mathrm{lowbit}(n)))$.

那么暴力递推的代码如下:

(code?)

#include<bits/stdc++.h>
using namespace std;
long long f[100005];
signed main()
{
    long long n;
    scanf("%lld",&n);
    f[0]=0;
    f[1]=1;
    for(long long i=2;i<n;++i)   f[i]=f[i-1]+(i^(i^(i&-i)));
    printf("%lld\n",f[n-1]);
    return 0;
}

仔细观察一下递推式,$n\ \mathrm{xor}\ (n\ \mathrm{xor}\ \mathrm{lowbit}(n))$ 不就是 $\mathrm{lowbit}(n)$ 嘛!

那么为题转化为求 $\mathrm{lowbit}$ 前缀和.

通过打一个 $\mathrm{lowbit}$ 表的方法,我们发现 $\mathrm{lowbit}$ 的值十分有规律,就像这种形式:

$$ \texttt{1 2 1 4 1 2 1 8 1 2 1 4 1 2 1 16 1 2 1 4 1 2 1 8 1 2 1 4 1 2 1 32}\cdots $$

其实这种规律要证明也很方便,只要根据二进制数末尾的情况即可得知.

虽然这个规律没啥用,但是启发了我们按位统计贡献的方法在 $\Theta(1)$ 空间 $\Theta(\log_{2}n)$ 的时间内计算出了 $\mathrm{lowbit}$ 前缀和.

具体方法请参考代码.

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
signed main()
{
    LL n;
    scanf("%lld",&n);
    LL ans=0,app=1,low=n;
    while(low>1)  ans+=app*(low>>1),low-=(low>>1),app<<=1;
    printf("%lld\n",ans);
    return 0;
}

Description

Link.

给你一棵树,放置守卫在某个点上面需要一定代价和一定的有效范围。让你覆盖若干指定点,求最小代价

Solution

算法标签:

$\ \ \ \ \ \ \ \ \ $ 树DP

DP状态定义:

$\ \ \ \ \ \ \ \ \ $ 说实话这道题定状态不好定。

$\ \ \ \ \ \ \ \ \ $ 那么我们从头来看,当 $d =0$ 的时候,我们就是在求树的最大独立集,定义显而易见。

$\ \ \ \ \ \ \ \ \ $ $d\neq 0$ 我们可以照搬原来的定义,把它扩展一下。


$\ \ \ \ \ \ \ \ \ $ $f_{i,j}$ 表示以 $i$ 为根结点的子树已经完全被覆盖让然后还能向上覆盖 $j$ 层的最小代价

$\ \ \ \ \ \ \ \ \ $ $g_{i,j}=$ 表示以 $i$ 为根结点的子树还有 $j$ 层没有覆盖的最小代价


$\ \ \ \ \ \ \ \ \ $ 需要注意的是 $j$ 本质上是带有方向性的,可以类比向量的概念。

$\ \ \ \ \ \ \ \ \ $ 边界条件很显然,$f_{i,0}=val_{i}$ 此时当前结点需要被覆盖。

$\ \ \ \ \ \ \ \ \ $ 其他情况:

$$ \begin{cases} f_{i,j}=val_{i},j\in [1,d] \\ \displaystyle f_{i,j}=\infty,j=d+1 \end{cases} $$

$\ \ \ \ \ \ \ \ \ $ 状态转移方程倒是比较好想,这里就不再赘述。

#include <cstdio>
#include <algorithm>
#include <queue>

char buf[1 << 21], *p1 = buf, *p2 = buf;
#ifndef ONLINE_JUDGE
#define gc() getchar()
#else
#define gc() (p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 1 << 21, stdin), p1 == p2) ? EOF : *p1++)
#endif
#define is_number (ch >= '0' && ch <= '9')

template < typename Type >
void read(Type& a) {
    a = 0; bool f = 0; char ch;
    while (!(ch = gc(), is_number)) if (ch == '-') f = 1;
    while (is_number) a = (a << 3) + (a << 1) + (ch ^ '0'), ch = gc();
    a = (f ? -a : a);
}

template < typename Type, typename... Args >
void read(Type& t, Args&... args) {
    read(t), read(args...);
}

int val[500005], f[500005][25];
int g[500005][25], vis[500005];
int n, m, d, tot, head[500005];
int nxt[1000005], to[1000005];
std::vector < std::vector < int > > G(500005);

void add(int x, int y) {
    to[++tot] = y;
    nxt[tot] = head[x];
    head[x] = tot;
    G[x].push_back(y);
    G[y].push_back(x);
}

void DP(int x, int fa) {
    if (vis[x]) g[x][0] = f[x][0] = val[x];
    for (int i = 1; i <= d; ++i) f[x][i] = val[x];
    f[x][d + 1] = 0x3f3f3f3f;
    for (int i = head[x]; i; i = nxt[i]) {
        int y = to[i];
        if (y ^ fa) {
            DP(y, x);
            for (int j = d; j >= 0; --j)
                f[x][j] = std::min(f[y][j + 1] + g[x][j + 1], f[x][j] + g[y][j]);
            for (int j = d; j >= 0; --j)
                f[x][j] = std::min(f[x][j + 1], f[x][j]);
            g[x][0] = f[x][0];
            for (int j = 1; j <= d + 1; ++j)
                g[x][j] += g[y][j - 1];
            for (int j = 1; j <= d + 1; ++j)
                g[x][j] = std::min(g[x][j - 1], g[x][j]);
        }
    }
}

signed main() {
    read(n, d);
    for (int i = 1; i <= n; ++i) read(val[i]);
    read(m);
    for (int i = 0, x; i < m; ++i) read(x), vis[x] = 1;
    for (int i = 1, x, y; i < n; ++i) read(x, y), add(x, y), add(y, x);
    DP(1, 0);
    printf("%d\n", g[1][0]);
}

NOI-Online-T1-序列

img spfaed

其实这道题是全场最难的……

我这里给出一种并查集的做法。

首先我们把操作2中的 $u$ 和 $v$ 合并

对于操作1我们可以把他转化为操作2来做。

比如我们针对操作1给出 $(u,v)$ 和 $(v,t)$ 两条边,对 $(u,v)$ 进行同增,对 $(v,t)$ 进行同减。

这样就变成了 $u++,t--$ 了。

然后我们把操作2缩点,然后把操作1的边连到操作2缩的点上。

然后对操作1合并。

此时,图中的每个点的度数最多为一。

那么对于一条边 $(x,y)$ 如果 $a_{x}-b_{x}=a_{y}-b_{y}$ 那么就是YES;

对于一个自环 $(x,x)$ 如果 $(a_{x}-b_{x})$ 为偶数,那么就是YES;

对于一个度数为零的点 $x$ 如果 $a_{x}=b_{x}$ 那么就是YES;

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <queue>

using namespace std;

char buf[1 << 21], *p1 = buf, *p2 = buf;
#ifndef ONLINE_JUDGE
#define gc() getchar()
#else
#define gc() (p2 == p1 && (p2 = (p1 = buf) + fread(buf, 1, 1 << 21, stdin), p1 == p2) ? EOF : *p1++)
#endif
#define is_number (ch >= '0' && ch <= '9')

template < typename Type >
void read(Type& a) {
    a = 0; bool f = 0; char ch;
    while (!(ch = gc(), is_number)) if (ch == '-') f = 1;
    while (is_number) a = (a << 3) + (a << 1) + (ch ^ '0'), ch = gc();
    a = (f ? -a : a);
}

template < typename Type, typename... Args >
void read(Type& t, Args&... args) {
    read(t), read(args...);
}

const int MAXN = 2e5 + 5;
int T, n, m, vis[MAXN];
int u[MAXN], v[MAXN];
int a[MAXN], b[MAXN];
int nxt[MAXN], to[MAXN];
int head[MAXN], tot;
struct UnionFindSet {
    int fa[MAXN];

    void init(int n) {
        for (int i = 1; i <= n; ++i)
            fa[i] = i;
    }

    int find(int x) {
        if (x ^ fa[x]) fa[x] = find(fa[x]);
        return fa[x];
    }

    void merge(int x, int y) {
        int u = find(x);
        int v = find(y);
        if (u ^ v) {
            fa[v] = u;
            a[u] += a[v];
            b[u] += b[v];
        }
    }
} ufs;

void add(int x, int y) {
    to[++tot] = y;
    nxt[tot] = head[x];
    head[x] = tot;
}

signed main() {
    for (read(T); T; --T) {
        read(n, m);
        tot = 0;
        memset(head, 0, sizeof head);
        ufs.init(n);
        for (int i = 1; i <= n; ++i) read(a[i]);
        for (int i = 1; i <= n; ++i) read(b[i]);
        for (int i = 1, opt; i <= m; ++i) {
            read(opt, u[i], v[i]);
            if (opt ^ 1) vis[i] = 1, ufs.merge(u[i], v[i]), --i, --m;
        }
        for (int i = 1; i <= m; ++i) {
            add(ufs.find(u[i]), ufs.find(v[i]));
            add(ufs.find(v[i]), ufs.find(u[i]));
        }
        for (int i = 1; i <= n; ++i) {
            int t = ufs.find(to[head[i]]);
            for (int x = nxt[head[i]]; x; x = nxt[x])
                ufs.merge(t, ufs.find(to[x]));
        }
        for (int i = 1; i <= n; ++i) {
            if (head[i]) {
                int x = ufs.find(i);
                int y = ufs.find(to[head[i]]);
                if (x ^ y) {
                    if ((a[x] - b[x]) ^ (a[y] - b[y])) {
                        puts("NO");
                        goto there;
                    }
                }
                else {
                    if ((a[x] - b[y]) & 1) {
                        puts("NO");
                        goto there;
                    }
                }
            }
            else if (ufs.fa[i] == i) {
                if (a[i] ^ b[i]) {
                    puts("NO");
                    goto there;
                }
            }
        }
        puts("YES");
        there: ;
    }
}

NOI-Online-T2-冒泡排序

这道题我在考场上的做法很玄,本来是奔着40pts的部分分去的,结果爆零了(至今没找到原因)

我们设

$$bigger_{i}=\sum_{j=1}^{i-1}[a_{j}>a_{i}]$$

显然逆序对数量为 $\sum bigger$

于是问题就转化为了动态维护 $bigger$。

手玩几组数据后我们可以发现,每轮冒泡 $bigger$ 都会有一下的变化:

$$bigger_{i}=\max\{bigger_{i}-1,0\}$$

于是树状数组维护即可

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <queue>

char buf[1 << 21], *p1 = buf, *p2 = buf;
#ifndef ONLINE_JUDGE
#define gc() getchar()
#else
#define gc() (p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 1 << 21, stdin), p1 == p2) ? EOF : *p1++)
#endif
#define is() (ch >= '0' && ch <= '9')
template < typename Type >
void read(Type& a) {
    a = 0; bool f = 0; char ch;
    while (!(ch = gc(), is())) if (ch == '-') f = 1;
    while (is()) a = (a << 3) + (a << 1) + (ch ^ '0'), ch = gc();
    a = (f ? -a : a);
}

template < typename Type, typename... Args >
void read(Type& t, Args&... args) {
    read(t), read(args...);
}

using namespace std;

const int MAXN = 2e5 + 5;
int n, m, bigger[MAXN], bucket[MAXN], a[MAXN];
long long bit[MAXN], init_inver_tot;

void Update(int x, long long y) {
    for (; x <= n; x += x & -x) bit[x] += y;
}

long long GetAnswers(int x) {
    long long res = 0;
    for (; x; x -= x & -x) res += bit[x];
    return res;
}

signed main() {
    read(n, m);
    for (int i = 1; i <= n; ++i) read(a[i]), init_inver_tot += (bigger[i] = i - 1 - GetAnswers(a[i])), bucket[bigger[i]]++, Update(a[i], 1);
    memset(bit, 0, sizeof bit), Update(1,  init_inver_tot), init_inver_tot = 0;
    for (int i = 0; i < n; ++i) init_inver_tot += 1LL * bucket[i], Update(i + 1 + 1, init_inver_tot - n);
    for (int i = 0, op, x; i < m; ++i) {
        read(op, x);
        if (n - 1 < x) x = n - 1;
        if (op ^ 2) {
            if (a[x + 1] > a[x]) {
                swap(a[x], a[x + 1]);
                swap(bigger[x], bigger[x + 1]);
                Update(1, 1);
                Update(bigger[x + 1]++ + 2, -1);
            }
            else {
                swap(a[x], a[x + 1]);
                swap(bigger[x], bigger[x + 1]);
                Update(1, -1);
                Update(--bigger[x] + 2, 1);
            }
        }
        else printf("%lld\n", GetAnswers(x + 1));
    }
    return 0;
}

NOI-Online-T3-最小环

全场最水的一道题,但是可怕的心理作用还是让我放弃了这道题。

首先有一个显然的结论,我们需要把这 $n$ 个数分为 $\gcd(n,k)$ 个环。

虽说是显然但是不画画图还真的玩不动

给一下图示意一下:

Annotation 2020-03-11 094939.png

图中那个看起来像个五角星的东西其实就是个环

Annotation 2020-03-11 095158.png

这个图中有 $\gcd(n,k)$ 个环,这就是我们的结论。

具体到实现,我们采用的是预处理出所有答案。

还有 $k=0$ 的情况需要特殊处理一下

#include <cstdio>
#include <algorithm>
#include <queue>

using namespace std;

const int MAXN = 5e5 + 5;
vector < int > integer(MAXN);
vector < long long > ans(MAXN);

int gcd(int x, int y) {
    return !y ? x : gcd(y, x % y);
}

signed main() {
    int n, k;
    scanf("%d %d", &n, &k);
    for (int i = 1; i <= n; ++i) scanf("%d", &integer.at(i)), ans.at(0) += (long long)integer.at(i) * (long long)integer.at(i);
    sort(integer.begin() + 1, integer.begin() + 1 + n);
    for (int i = 1; i <= (n >> 1); ++i) {
        if (!(n % i)) {
            int t = n / i;
            vector < int > process(MAXN);
            int tot = 0;
            for (int j = 2; j < t; j += 2) process.at(++tot) = j;
            process.at(++tot) = t;
            for (int j = t - 1 - (t & 1); j > 0; j -= 2) process.at(++tot) = j;
            for (int j = t + 1; j <= n; ++j) process.at(j) = process.at(j - t) + t;
            for (int j = 1; j <= n; ++j)
                if (!(j % t)) ans.at(i) += (long long)integer.at(process.at(j)) * (long long)integer.at(process.at(j + 1 - t));
                else ans.at(i) += (long long)integer.at(process.at(j)) * (long long)(integer.at(process.at(j + 1)));
        }
    }
    for (int i = 0, x; i < k; ++i) scanf("%d", &x), printf("%lld\n", ans.at(x ? gcd(n, x) : 0));
    return 0;
}

总结

(其实我不是很会写这玩意儿)

果然心理素质还是不行……错过了T3这样的水题。

总体来说,把握住机会,把题目都当作大白菜(雾)。

然后就是多去做题吧,题量多少都不嫌多。

就这样(

普及组口胡

说了是口胡所以没代码不保证正确/xyx

至于题目难度这是NOIOL不是NOIpOL给了出题人放飞自我的空间。

T1:普通NOIp普及难度,各位巨佬随便切

T2: 基础多项式,会的人就很套路。不会的话就n方dp骗骗分

T3: 这道题我不太确定,应该是Floyd+矩阵。

完结撒六花