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

标签 dp 下的文章

「CF 1485A」Add and Divide

Link.

贪心。枚举 $[b,b+\log_{2}\text{range}]$ 然后取个 $\min$。

#include<cstdio>
#include<algorithm>
using namespace std;
int t,a,b,ans;
int search(int bas)
{
    if(bas>1)
    {
        int tmp=a,res=0;
        while(tmp>0)
        {
            tmp/=bas;
            res++;
        }
        return res;
    }
    else    return 1e9;
}
int main()
{
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d %d",&a,&b);
        if(a==b)    printf("%d\n",2);
        else if(a<b)    printf("%d\n",1);
        else
        {
            ans=1e9;
            for(int i=b;i<=b+233;++i)    ans=min(ans,search(i)+i-b);
            printf("%d\n",ans);
        }
    }
    return 0;
}

「CF 1485B」Replace and Keep Sorted

Link.

每个元素都可以上下摇摆于是预处理前缀差分和和后缀差分和(因为是 strictly increasing 所以要减 $1$)即可。

#include<cstdio>
int n,m,k,a[100010],fro[100010],rea[100010];
void pre()
{
    for(int i=1;i<=n;++i)    fro[i]=a[i]-a[i-1]-1;
    for(int i=n;i>=1;--i)    rea[i]=a[i+1]-a[i]-1;
    for(int i=1;i<=n;++i)    fro[i]+=fro[i-1];
    for(int i=n;i>=1;--i)    rea[i]+=rea[i+1];
}
int main()
{
    scanf("%d %d %d",&n,&m,&k);
    for(int i=1;i<=n;++i)    scanf("%d",&a[i]);
    pre();
    while(m--)
    {
        int l,r;
        scanf("%d %d",&l,&r);
        printf("%d\n",k-a[r]+a[l]-1+fro[r]-fro[l]+rea[l]-rea[r]);
    }
    return 0;
}

「CF 1485C」Floor and Mod

Link.

$$ a\bmod b=\lfloor\frac{a}{b}\rfloor=k \\ \rightarrow a=kb+k\rightarrow a=(b+1)k\rightarrow k=\frac{a}{b+1} \\ k<b\rightarrow k^{2}<k(b+1)=a\le x\rightarrow 1\le k\le\sqrt{x} \\ 1\le a\le x\rightarrow 1\le(b+1)k\le x\rightarrow1\le b\le\frac{x}{k}-1 \\ \rightarrow\text{ans}=\sum_{k=1}^{\sqrt{x}}\max(0,\min(y,\frac{x}{k}-1)-k) $$

#include<cstdio>
#include<algorithm>
using namespace std;
const long long zero=0ll;
long long t,x,y,ans;
int main()
{
    scanf("%lld",&t);
    while(t--)
    {
        scanf("%lld %lld",&x,&y);
        ans=0;
        for(long long i=1;i*i<=x;++i)    ans+=max(zero,min(y,x/i-1)-i);
        printf("%lld\n",ans);
    }
    return 0;
}

「CF 1485D」Multiples and Power Differences

Link.

  • $(i+j)\bmod 2=1$:$b_{i,j}=\text{lcm}(1,\cdots,16)$。
  • $(i+j)\bmod 2=0$:$b_{i,j}=\text{lcm}(1,\cdots,16)+a_{i,j}^{4}$。

#include<cstdio>
int n,m,x;
int main()
{
    scanf("%d %d",&n,&m);
    for(int i=1;i<=n;++i)
    {
        for(int j=1;j<=m;++j)
        {
            scanf("%d",&x);
            if((i+j)&1)    printf("%d ",720720);
            else    printf("%d ",720720+x*x*x*x);
        }
        printf("\n");
    }
    return 0;
}

「CF 1485E」Move and Swap

Link.

blue 因为就是一直往下跑,所以一次操作在哪里不影响。

于是设 $f_{u}$ 为操作完毕后 red 跑到 $u$ 的 maximum value。

  • $v\in\text{son}(u)$ 为 red:此时没发生 swapping,$f_{u}=f_{v}+|a_{u}-a_{v}|$。
  • $v\in\text{son}(u)$ 为 blue:此时发生了 swapping,那么枚举 $v$ 的同层结点 $anov$,$f_{u}=f_{anov}+|a_{u}-a_{anov}|$。

#include<queue>
#include<cstdio>
#include<algorithm>
using namespace std;
const long long INF=1e18;
vector<int> e[200010],same[200010];
int t,n,dep[200010],fa[200010],leaf;
long long a[200010],f[200010];
void dfs(int x,int las)
{
    dep[x]=dep[las]+1;
    fa[x]=las;
    leaf=max(leaf,dep[x]);
    for(int i=0;i<e[x].size();++i)
    {
        int y=e[x][i];
        if(y^las)    dfs(y,x);
    }
}
void DP(int d)
{
    for(int i=d;i>1;--i)
    {
        long long mn=INF,mx=-INF,one=-INF,ano=-INF;
        for(int j=0;j<same[i].size();++j)
        {
            mn=min(mn,a[same[i][j]]);
            mx=max(mx,a[same[i][j]]);
        }
        for(int j=0;j<same[i].size();++j)    f[fa[same[i][j]]]=max(f[fa[same[i][j]]],max(a[same[i][j]]-mn,mx-a[same[i][j]])+f[same[i][j]]);
        for(int j=0;j<same[i].size();++j)
        {
            one=max(one,f[same[i][j]]+a[same[i][j]]);
            ano=max(ano,f[same[i][j]]-a[same[i][j]]);
        }
        for(int j=0;j<same[i].size();++j)    f[fa[same[i][j]]]=max(f[fa[same[i][j]]],max(one-a[same[i][j]],ano+a[same[i][j]]));
    }
}
int main()
{
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d",&n);
        for(int i=2;i<=n;++i)
        {
            int x;
            scanf("%d",&x);
            e[x].push_back(i);
            e[i].push_back(x);
        }
        for(int i=2;i<=n;++i)    scanf("%d",&a[i]);
        dfs(1,0);
        for(int i=1;i<=n;++i)    same[dep[i]].push_back(i);
        DP(leaf);
        printf("%lld\n",f[1]);
        for(int i=1;i<=n;++i)
        {
            f[i]=dep[i]=fa[i]=0;
            same[i].clear();
            e[i].clear();
        }
        leaf=0;
    }
    return 0;
}

「CF 1485F」Copy or Prefix Sum

Link.

「ARC 109A」Hands

Link.

讨论即可,除了煞笔出题人写了个死马的题面。

#include<cstdio>
#include<algorithm>
using namespace std;
int a,b,x,y,ans;
int main()
{
    scanf("%d%d%d%d",&a,&b,&x,&y);
    if(a>b)    printf("%d\n",min(x<<1,y)*max(0,abs(a-b)-1)+x);
    else    printf("%d\n",min(x<<1,y)*max(0,abs(a-b))+x);
    return 0;
}

「ARC 109B」log

Link.

要贪心的取的话,肯定是先把 $n+1$ 取了,然后我们来二分。

$1-n$ 分别有 $n+1$ 到 $2$ 种方法可以组成他。

然后来考虑怎么 check。

可以知晓,如果没有这一块多的木块,就一定需要 $n$ 块木头。

然后就按开头那个贪心,把 $n+1$ 从 $1$ 分完,剩下的再依次分。

#include<cstdio>
unsigned long long n;
bool check(unsigned long long x)
{
    return (x*(x+1)>>1)<=n+1;
}
unsigned long long search(unsigned long long l,unsigned long long r)
{
    unsigned long long res=0;
    while(l<=r)
    {
        unsigned long long mid=(l+r)>>1;
        if(check(mid))
        {
            l=mid+1;
            res=n-mid+1;
        }
        else    r=mid-1;
    }
    return res;
}
int main()
{
    scanf("%llu",&n);
    printf("%llu\n",search(1,2e9));
    return 0;
}

「ARC 109C」Large RPS Tournament

Link.

$2^{k}$!好耶!!!

考虑倍增 DP。设 $f_{i,j}$ 为区间 $[i,i+2^{j}-1]$ 的 winner's hand。

设计一个函数 $\text{tournament}(P,Q)$ 为 $P$、$Q$ 比武后的赢家。

转移即

$$ f_{i,j}=\text{tournament}(f_{i,j-1},f_{i+2^{j-1},j-1}) $$

当然你不能直接用 $2^{k}$ 当成序列来做,反正他是循环节我们直接做 $k$ 轮最后合并即可。

实际实现时不需要这么写(主要是写不来)(好像可以记忆化?)。

#include<string>
#include<cstdio>
#include<cstring>
#include<iostream>
using namespace std;
string s;
int n,k;
char tour(char one,char ano)
{
    if(one=='R')
    {
        if(ano=='R')    return 'R';
        else if(ano=='P')    return 'P';
        else    return 'R';
    }
    else if(one=='P')
    {
        if(ano=='R')    return 'P';
        else if(ano=='P')    return 'P';
        else    return 'S';
    }
    else
    {
        if(ano=='R')    return 'R';
        else if(ano=='P')    return 'S';
        else    return 'S';
    }
}
int main()
{
    scanf("%d%d",&n,&k);
    cin>>s;
    while(k--)
    {
        string tmp=s+s;
        for(int i=0;i<n;++i)    s[i]=tour(tmp[i<<1],tmp[i<<1|1]);
    }
    printf("%c\n",s[0]);
    return 0;
}

「ARC 109D」L

图画出来差不多,向目标进发,步数下界就出来了 $\max\{|x|,|y|\}$。

这张图是在这里嫖的:

注意特判一些奇怪的情况,具体自己看代码吧吧吧吧。

#include<cstdio>
#include<algorithm>
using namespace std;
int t,onex,oney,anox,anoy,exx,exy,finalx,finaly;
int main()
{
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d%d%d%d%d%d",&onex,&oney,&anox,&anoy,&exx,&exy);
        finalx=min(onex,min(anox,exx));
        finaly=min(oney,min(anoy,exy));
        finalx=(finalx<<1)+(finalx!=onex)+(finalx!=anox)+(finalx!=exx)-1;
        finaly=(finaly<<1)+(finaly!=oney)+(finaly!=anoy)+(finaly!=exy)-1;
        printf("%d\n",max(abs(finalx),abs(finaly))+((finalx==finaly)&&(finalx>1||finalx<0)));
    }
    return 0;
}

「ARC 109E」1D Reversi Builder

Link.

「ARC 109F」1D Kingdom Builder

Link.

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

首先否决往大了想。

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

然后不会了。