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

标签 geometry 下的文章

Part. 1 FFT

Part. 1-1 Main

对于一个 $n$ 次多项式 $F(x)=\sum_{i=0}^{n}a_{i}x^{i}$,在平面直角坐标系中可以由 $n+1$ 个点唯一确定。

考虑带什么样的 $x$ 进去,能够快速计算 $x^{n}$ 并且有一定的性质,DFT 采用的是复单位根。

那么 DFT 就是把 $F(x)$ 转为点值表示。我们来推式子:

先令 $L(x)=\sum_{i=0}^{\lfloor\frac{n}{2}\rfloor-1}a_{2i}x^{2i},R(x)=\sum_{i=0}^{\lfloor\frac{n}{2}\rfloor-1}a_{2i+1}x^{2i}$。

$$ \begin{aligned} F(\omega_{n}^{k})&=L((\omega_{n}^{k})^{2})+\omega_{n}^{k}R((\omega_{n}^{k})^{2}) \\ &=L(\omega_{n}^{2k})+\omega_{n}^{k}R(\omega_{n}^{2k}) \\ &=L(\omega_{\lfloor\frac{n}{2}\rfloor}^{k})+\omega_{n}^{k}R(\omega_{\lfloor\frac{n}{2}\rfloor}^{2k}) \\ \end{aligned} $$

同时:

$$ \begin{aligned} F(\omega_{n}^{k+\lfloor\frac{n}{2}\rfloor})&=L(\omega_{n}^{2k})+\omega_{n}^{k+\lfloor\frac{n}{2}\rfloor}R(\omega_{n}^{2k}) \\ &=L(\omega_{\lfloor\frac{n}{2}\rfloor}^{k})-\omega_{n}^{k}R(\omega_{\lfloor\frac{n}{2}\rfloor}^{k}) \end{aligned} $$

于是你直接分治,这是 DFT,注意要把多项式长度调整为 $2$ 的幂。

递归常数大,考虑迭代。你会发现分治后的序列与原序列的关系是下标的二进制反转,然后就完了。

void fft(Poly &f,int op)
{
    for(int i=0;i<lim;++i)    if(i<rev[i])    swap(f[i],f[rev[i]]);
    for(int len=2;len<=lim;len<<=1)
    {
        comp bas(cos(2*bh_pi/len),op*sin(2*bh_pi/len));
        for(int fr=0;fr<lim;fr+=len)
        {
            comp now(1,0);
            for(int ba=fr;ba<fr+(len>>1);++ba,now*=bas)
            {
                comp tmp=now*f[ba+(len>>1)];
                f[ba+(len>>1)]=f[ba]-tmp;
                f[ba]+=tmp;
            }
        }
    }
    if(op==-1)    for(int i=0;i<lim;++i)    f[i]/=lim;
}

本来十分抗拒,但 GM 强制。

「ABC 183A」ReLU

Link.

略。

#include<cstdio>
int main()
{
    long long n;
    scanf("%lld",&n);
    printf("%lld\n",n>0?n:0);
    return 0;
}

「ABC 183B」Billiards

Link.

设答案坐标 $A(m,n)$,然后算出 $y_{AG}$ 解析式,再带 $x=S'_{x}$,$S'$ 是 $S$ 关于直线 $x=m$ 的对称点,得出来的 $y$ 要等于 $n$,然后列个方程解出来答案为 $\frac{S_{x}G_{y}+G_{x}S_{y}}{S_{y}+G_{y}}$。

#include<cstdio>
double sx,sy,gx,gy;
int main()
{
    scanf("%lf%lf%lf%lf",&sx,&sy,&gx,&gy);
    printf("%lf\n",(sx*gy+gx*sy)/(sy+gy));
    return 0;
}

「ABC 183C」Travel

Link.

全排列枚举算答案即可。

#include<cstdio>
#include<vector>
#include<algorithm>
using namespace std;
vector<int> per;
int n,ans;
long long k,tim[20][20];
int main()
{
    scanf("%d%lld",&n,&k);
    for(int i=1;i<=n;++i)
    {
        for(int j=1;j<=n;++j)    scanf("%lld",&tim[i][j]);
    }
    per.resize(n+2);
    for(int i=1;i<=n;++i)    per[i]=i;
    per[n+1]=1;
    do
    {
        long long sum=0;
        for(int i=2;i<=n+1;++i)    sum+=tim[per[i-1]][per[i]];
        if(sum==k)    ++ans;
    }while(next_permutation(per.begin()+2,per.end()-1));
    printf("%d\n",ans);
    return 0;
}

「ABC 183D」Water Heater

Link.

前缀和。

#include<cstdio>
int n,s[200010],t[200010],p[200010],w;
long long dif[200010];
int main()
{
    scanf("%d%d",&n,&w);
    for(int i=1;i<=n;++i)    scanf("%d%d%d",&s[i],&t[i],&p[i]);
    for(int i=1;i<=n;++i)
    {
        dif[s[i]]+=p[i];
        dif[t[i]]-=p[i];
    }
    for(int i=1;i<=200000;++i)    dif[i]+=dif[i-1];
    for(int i=0;i<=200000;++i)
    {
        if(dif[i]>w)
        {
            printf("No\n");
            return 0;
        }
    }
    printf("Yes\n");
    return 0;
}

「ABC 183E」Water Heater

Link.

递推完了。

#include<cstdio>
const int mod=1e9+7;
long long ans;
int n,m,mp[2010][2010],row[2010],col[2010],dia[5010];
char str[2010];
int add(long long a,long long b)
{
    if(a+b>=mod)    return (a+b)%mod;
    else    return a+b;
}
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;++i)
    {
        scanf("%s",str+1);
        for(int j=1;j<=m;++j)
        {
            if(str[j]=='.')    mp[i][j]=0;
            else    mp[i][j]=1;
        }
    }
    int lay=2e3;
    for(int i=1;i<=n;++i)
    {
        for(int j=1;j<=m;++j)
        {
            if(mp[i][j])
            {
                row[i]=0;
                col[j]=0;
                dia[i-j+lay]=0;
            }
            else
            {
                int tmp=add(add(row[i],col[j]),dia[i-j+lay]);
                if(i==1&&j==1)    ++tmp;
                row[i]=add(row[i],tmp);
                col[j]=add(col[j],tmp);
                dia[i-j+lay]=add(dia[i-j+lay],tmp);
                ans=tmp;
            }
        }
    }
    printf("%d\n",ans);
    return 0;
}

「ABC 183F」Confluence

Link.

并查集板。

#pragma GCC diagnostic error "-std=c++11"
#include<map>
#include<cstdio>
using namespace std;
map<int,int> mp[200010];
int n,m,fa[200010];
void makeset()
{
    for(int i=1;i<=n;++i)    fa[i]=i;
}
int findset(int x)
{
    if(x^fa[x])    fa[x]=findset(fa[x]);
    return fa[x];
}
void mergeset(int x,int y)
{
    x=findset(x);
    y=findset(y);
    if(x^y)
    {
        if(mp[x].size()>mp[y].size())
        {
            fa[y]=x;
            for(auto p:mp[y])    mp[x][p.first]+=p.second;
        }
        else
        {
            fa[x]=y;
            for(auto p:mp[x])    mp[y][p.first]+=p.second;
        }
    }
}
int main()
{
    scanf("%d%d",&n,&m);
    makeset();
    for(int i=1;i<=n;++i)
    {
        int x;
        scanf("%d",&x);
        mp[i][x]++;
    }
    while(m--)
    {
        int opt,opx,opy;
        scanf("%d%d%d",&opt,&opx,&opy);
        if(opt==1)    mergeset(opx,opy);
        else
        {
            int tmp=findset(opx);
            printf("%d\n",mp[tmp][opy]);
        }
    }
    return 0;
}

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$。

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

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;
}