CSP-S 2019 雜記

update 2019.11.18 考完後的第一感覺node

update 2019.11.24 我校某優秀學子把全SD的選手程序全測了一遍(太狠了,因而就知道了大概的慘淡成績,大概是可憐的省二選手了ios

(由於太菜,因此港真是去玩了c++

感受此次CSP真的發揮上巨差無疑了。數組

考場的機子巨巨巨巨巨差,32位機不說,且配置巨低,試機的時候A+B Problem+快讀+freopen用了4s多(當場自閉,好像不止我一我的這樣?巨影響作題體驗(雖然作題體驗這東西與沒帶腦子相比已經不重要了;大數據

旁邊是一個青島二中的神仙小姐姐,還來LCEZ參加過數學夏令營,對我校女生的短髮留有深入的印象。優化

考試的兩天過的還好,day1考前總擔憂本身會CE,後來看上去應該是沒有。考完day1以後整我的心情平靜,day2考的毫無波瀾(反正不會就不會了ui

\(\mathcal{Day \ 1}:\)

day1考前仍是很焦慮的,生怕本身CE(如今想一想十分迷惑,考完以後吃了午餐,和yrq一塊兒出去逛了逛校園,排解了一下鬱悶的心情spa

回來之後看了day 1 T1 T2的題解。code

update:回來指從csp考場回到學校,並非day1考完的下午blog

T1:格雷碼

看上去好像T1思路是對了的叭?沒敢仔細看細節和代碼,懼怕發現本身代碼寫鍋了而後整我的崩潰掉;不出意外的話大概是能夠拿到至少95pts的(對於ull的那5pts好多人說過不去,就很玄乎;只能跪求老天保佑我

update:從學長測得的分數是100pts,大概是qbzt的數據沒有卡ull,當我交到luogu上才意識到,原來ull的數據範圍是\(0 \to 2^{64}-1\),因而若是最後一個點給的是\(2^{64}-1\)的話,我就很愉快的掛了(由於我加了1

\(\mathbb{SOLUTION}\):

我屬於優秀的打表找規律型人才,因而這道題也是經過打表找到的規律:

並無按照題目所給的生成方式找規律,而是自成一派

能夠觀察:

從最高位開始填,

\(\mathbb{A}.\)若是上一個區間是上上個區間的左半區間:

\(\mathfrak{a}.\)若是這一項在它所在當前區間的左半側(小於等於區間長度的一半,那麼在這一位填0,

\(\mathfrak{b}.\)若是在右半側,填1;

\(\mathbb{B}.\)若是上一個區間是上上個區間的右半區間:

\(\mathfrak{a}.\)若是這一項在它所在當前區間的左半側(小於等於區間長度的一半,那麼在這一位填1

\(\mathfrak{b}.\)若是在右半側,填1;

注意是\(\color{ Gold }{當前}\)區間,這裏當前區間的定義大概是:不斷二分縮小範圍後的區間\(\color{SkyBlue}{[l,r]}\),舉個例子:

假設要找四位格雷碼的第5項(記j=5(從1開始記項

如下從高位到低位分別爲第一位到第n位;

開始時的區間顯然是整個四位格雷碼 \([1,2^4]\),5顯然小於區間的一半\(2^3\),最高位填0,區間範圍縮小爲\([1,2^3]\),

\([1,2^3]\)的一半是\(2^2\),而\(5>2^2\),大於區間的一半,上一個區間是上上個區間的左半區間,第二位填1,區間變爲\([2^2+1,2^3]\),(代碼的實現中,若是是一個\([2^k+1,2^{k+1}]\)的區間,將其轉化爲了\([1,2^k]\)的區間,而將j相應的減掉\(2^k\))

因此當前區間轉化爲\([1,2^2],j=5-2^2=1,1<2^1\),小於等於區間一半,而且上個區間是上上個區間的右半區間,填1,區間變爲\([1,2],j=1\)

\(j<=2^0\),小於等於區間一半,而且上個區間是上上個區間的左半區間,所以填0;

因此第五項是0110;對照上表,嗯,是對的;

#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#include<string>
#include<iostream>
#define ll long long

using namespace std;


inline unsigned ll read() {
    unsigned ll ans=0;
    char last=' ',ch=getchar();
    while(ch>'9'||ch<'0') last=ch,ch=getchar();
    while(ch>='0'&&ch<='9') ans=(ans<<1)+(ans<<3)+ch-'0',ch=getchar();
    if(last=='-') ans=-ans;
    return ans;
    
}
int n;
unsigned ll k;
int a[200],cnt;

ll quick_pow(int p) {
    ll ans=1;
    ll a=2;
    while(p) {
        if(p&1) ans=ans*a;
        a*=a;
        p>>=1;
    }
    return ans;
}

void solve(unsigned ll k,int q,int kind) {
    //k:第幾項(有的時候會被-區間長度一半
    //q:填到第幾位
    //kind:上一個區間是上上個區間的左區間0/右區間1
    if(q==0) return ;
    ll d=quick_pow(q-1);
    if(kind==0) {
        if(k<=d) {
            a[++cnt]=0;
            solve(k,q-1,0);
        }
        else {
            a[++cnt]=1;
            solve(k-d,q-1,1);//始終保證區間在[1,2^f]中
        }
    } else {
        if(k<=d) {
            a[++cnt]=1;
            solve(k,q-1,0);
        }
        else {
            a[++cnt]=0;
            solve(k-d,q-1,1);
        }
    }
    
}

int main() {
    memset(a,0,sizeof(a));
    scanf("%d",&n);
    k=read();
    if(k==18446744073709551615) {//考場上並無考慮到的爆unsigned long long的狀況
        printf("1");
        for(int i=2;i<=n;i++)
            printf("0"); 
        return 0;
    } 
    k++;
    solve(k,n,0);
    for(int i=1;i<=n;i++) 
        printf("%d",a[i]);
    return 0;
}

T2:括號樹

T2考場上是真的毫無思路,連暴力都沒想明白怎麼打(,而後輸出了0或y的生日,指望得分 0(絕望

update:果然爆零,看來\(\mathfrak{yky}\)歐氣不夠啊\(\mathfrak{yky}\)

T2:各類暴力部分分(由於考場上啥也沒打出來就很很很難過,決心每一部分都要寫

1.指望20pts左右的徹底暴力:

\(\mathbb{SOLUTION}:\)

說真的我當時考場上真的連這個題的暴力都沒想到

暴力的思路很簡單,由於是鏈,能夠依次枚舉當前點i,而後枚舉區間,判斷這個區間是否合法,若是合法,ans++;

複雜度是\(O(n^4)\),實際並跑不滿;

#include<bits/stdc++.h>
#define ll long long

using namespace std;

inline ll read() {
    ll ans=0;
    char last=' ',ch=getchar();
    while(ch>'9'||ch<'0') last=ch,ch=getchar();
    while(ch>='0'&&ch<='9') ans=(ans<<1)+(ans<<3)+ch-'0',ch=getchar();
    if(last=='-') ans=-ans;
    return ans;
}

ll n;
char a[500010];

char Getchar() {
    char ch;
    do {
        ch=getchar();
    }while(ch!=')'&&ch!='(');
    return ch;
}
ll ans;
ll fa[500010];

bool check(int l,int r) {
    int top=0; 
    for(int i=l;i<=r;i++) {
        if(a[i]=='(') top++;
        else {
            if(top>0)
                top--;
            else 
                return 0; 
        } 
    }
    return top==0?1:0;
}

int main() {
    n=read();
    for(int i=1;i<=n;i++)
        a[i]=Getchar(); 
    for(int i=1;i<n;i++)
        fa[i+1]=read();
    ll k;
    for(int i=1;i<=n;i++) {
        k=0;
        for(int l=1;l<i;l++) {
            for(int r=l+1;r<=i;r++) {
                if(check(l,r))
                    k++;
            }
        }
        ans^=(k*i);
    }
    printf("%lld",ans);
    return 0;
}

2.鏈上的dp(55pts:

\(\mathbb{SOLUTION}:\)

定義一個lst[i],記錄從1~i,以i結尾的合法的括號序列有多少個

對於一個'('來講,顯然lst[i]=0;
因此考慮右括號:

舉例子手玩一下:

括號√ ( ) ) ( ) ) ( ( )
lst 0 1 0 0 1 0 0 0 1
sum 0 1 1 1 2 2 2 2 3
括號√ ( ) ( ) )
lst 0 1 0 0 0 1 1 2 0
sum 0 1 1 1 1 2 3 5 5

能夠發現,當一個括號是右括號(記位置爲i)的時候,首先要找是否有匹配的左括號。

​ 若是沒有能夠匹配的左括號,lst=0;

​ 若是有了匹配的左括號,lst的值至少爲1(顯然中間的部分都是合法的說 感性李姐

​ 而後再考慮匹配的左括號左邊是否能夠與當前的括號再拼成其餘的合法括號序列,這個時候咱們就要看匹配的左括號的左邊一個的lst(記這個位置爲t-1),顯然的,若是以t-1s結尾的括號能夠拼成合法的括號序列,那麼它與剛剛匹配的那段序列一樣能夠組成一個以最後的右括號結尾的合法序列。

​ 所以,\(lst[i]=lst[t-1]+1;\)

​ 用一個棧來維護左括號是再好不過了:遇到左括號壓入棧中,若是碰到一個匹配的右括號,退棧。

因而:

\(lst[i]=lst[t-1]+1,t=s[top]\\ans[i]=ans[i-1]+lst[i]\)

因而這就是鏈的部分分:

#include<bits/stdc++.h>
#define ll long long

using namespace std;

inline ll read() {
    ll ans=0;
    char last=' ',ch=getchar();
    while(ch>'9'||ch<'0') last=ch,ch=getchar();
    while(ch>='0'&&ch<='9') ans=(ans<<1)+(ans<<3)+ch-'0',ch=getchar();
    if(last=='-') ans=-ans;
    return ans;
}

ll n;
char a[500010];

char Getchar() {
    char ch;
    do {
        ch=getchar();
    }while(ch!=')'&&ch!='(');
    return ch;
}
ll Ans;
ll fa[500010];
ll lst[500010],s[500010];
ll ans[500010];
ll top;

int main() {
    n=read();
    for(int i=1;i<=n;i++)
        a[i]=Getchar(); 
    for(int i=1;i<n;i++)
        fa[i+1]=read();
    for(int i=1;i<=n;i++) {
        if(a[i]==')') {
            if(top!=0) {
                int t=s[top];
                top--;
                lst[i]=lst[t-1]+1;
            }
        }
        else 
            s[++top]=i; 
        ans[i]=ans[i-1]+lst[i];
        Ans^=ans[i]*i;
    }
    printf("%lld",Ans);
    return 0;
}

3.關於正解

\(\mathbb{SOLUTION}:\)

考慮把鏈上的dp轉移到樹上:
和在鏈上的dp很相似,由於是在樹上,所以只須要將dp式子中的\(t-一、i-1\)替換成\(fa[t]、fa[i]\)便可;

大體以下:

\(lst[u]=lst[fa[t]]+1,t=s[top];\\ans[u]=ans[fa[u]]+lst[u]\)

而後考慮應該如何維護s:

一路向下遞歸,顯然遞歸獲得的是一條鏈,所以往下遞歸的時候,按照鏈的遞歸方法可勁加就好,主要須要注意的在遞歸到底之後的回溯上:

\(\mathbb{A}.\)若是當前節點是一個右括號,

\(\mathfrak{a}.\)在這條鏈上,這個右括號找到了一個匹配的左括號。

顯然,左括號會在與右括號匹配過程當中從棧中彈出,而當咱們要回溯時,被彈出的節點要從新壓入棧中,來與其餘子樹中的右括號匹配,

\(\mathfrak{b}.\)固然,若是這個點沒有找到能夠匹配的左括號,咱們天然不須要再進棧。

\(\mathbb{B}.\)若是當前節點是左括號,那麼須要將當前節點退棧。

關於建圖的話,由於給出的是明確的父親兒子關係,所以能夠只建單向邊。

#include<bits/stdc++.h>
#define ll long long

using namespace std;

inline ll read() {
    ll ans=0;
    char last=' ',ch=getchar();
    while(ch>'9'||ch<'0') last=ch,ch=getchar();
    while(ch>='0'&&ch<='9') ans=(ans<<1)+(ans<<3)+ch-'0',ch=getchar();
    if(last=='-') ans=-ans;
    return ans;
}
char Getchar() {
    char ch;
    do {
        ch=getchar();
    }while(ch!=')'&&ch!='(');
    return ch;
}
const int mxn=500010;

ll n,Ans;
char a[mxn];
ll lst[mxn],ans[mxn];
ll s[mxn],top;
struct node {
    int to,nxt;
}e[mxn<<1];
ll ecnt,head[mxn],fa[mxn];
void add(int from,int to) {
    ++ecnt;
    e[ecnt].to=to;
    e[ecnt].nxt=head[from];
    head[from]=ecnt;
}

void dfs(int u) {
    int t=0;
    if(a[u]=='(') //左括號,入棧
        s[++top]=u;
    else {//右括號
        if(top!=0) {//能夠找到匹配的左括號
            t=s[top];
            lst[u]=lst[fa[t]]+1;
            top--;//退棧
        }
    }
    ans[u]=ans[fa[u]]+lst[u]; 
    Ans^=ans[u]*u;
    for(int i=head[u],v;i;i=e[i].nxt) {
        v=e[i].to;
        dfs(v);
    }
    if(t!=0) //A: a or b
        s[++top]=t;
    if(a[u]=='(') //B
        top--;
}

int main() {
    n=read();
    for(int i=1;i<=n;i++)
        a[i]=Getchar(); 
    for(int v=2,u;v<=n;v++) { 
        u=read();
        fa[v]=u;
        add(u,v);
    }
    dfs(1);
    printf("%lld",Ans);
    return 0;
}

T3:樹上的數

妄圖看懂而且寫會T3,而後我發現我錯了;

黑題果然不是白評的(哭

考場上的時候很是cǎo的看錯了題,認爲菊花圖和鏈的部分分仍是挺好拿的(然而事實上由於看錯題而爆零了

到如今luogu也只有107我的經過它?!

\(\mathcal{Day \ 2}\)

T1:Emiya家今天的飯

做爲day2 t1,它好難(哭

考場上:

*這個題暴力我都不會打,怎麼dfs啊(哭

誒?!lwy是否是講過一個相似的dfs?能夠先dfs行再dfs列是吧

因而果斷打了暴力

而後發現暴力並過不了大數據,因而嘗試改爲記憶化

調到了10點,仍是沒有調出來,因而果斷放棄(事實上並無很果斷,去看t2,t3

感受策略上錯了,若是打完暴力就跑去寫t2t3的話,或許就能少一個題爆零了(由於直到最後記憶化也沒有成功

但思路仍是要學會的

\(\mathbb{SOLUTION}\):

思路上的突破點在於Yazid的約束,「 他要求每種主要食材至多在一半的菜(即 \(\lfloor \frac{k}{2} \rfloor\)道菜)中被使用 」,那麼若是有一道食材被用在了超過一半的菜中,其他的每一種食材都不會超過一半的菜。由此,能夠採用容斥原理:每行至多選1個的方案-每行至多選一個,而且有一列選擇的超過了\(\lfloor\frac{k}{2}\rfloor\)道菜;

首先先從簡單的開始嘛:如何計算總方案:

定義數組\(s[i][0]\)表示第i行的菜品數的總和,\(g[i][j]\)表示前i行共選了j個數的方案,因而:

\(g[i][j]=g[i-1][j]+g[i-1][j-1]*s[i][0]\)

也就是前i行選j個數的方案由前i-1行選j個數的方案+前i-1行選j-1個數,第i行選一個數的方案推過來的;

那麼\(\sum\limits_{i=1}^n g[n][i]\)就是總方案數

再考慮不合法的狀況:

首先咱們要枚舉一列L,表示這一列的選擇超過了\(\lfloor\frac{k}{2}\rfloor\)道菜。

\(f[i][j][k]\)表示前i行,咱們枚舉的這一列L選了j道,除去L外的其餘列選了k道的方案數

\(s[i][L]\)表示第i行,除去\(a[i][L]\)這個食材所能作成的菜以外能作成的菜的和。

\(f[i][j][k]=f[i-1][j][k]+f[i-1][j-1][k]*a[i][L]+f[i-1][j][k-1]*s[i][L]\)

轉移方程分爲三部分:

\(\mathbb{A}.\)在第i行並不選菜品的方案數,就是上一行在L列選了j道,在其餘列選了k道的方案數

\(\mathbb{B}.\)在第i行選擇了一道在L列的菜品的方案,首先上一行在L列選擇了j-1道,在上一行的基礎上,有\(a[i][L]\)種選擇方案,能夠增長一道在L列的菜品,因而這種狀況的方案就是:\(f[i-1][j-1][k]*a[i][L]\)

\(\mathbb{C}.\)在第i行選擇了一道不是L列的菜品的方案,即\(f[i-1][j][k-1]*s[i][L]\)

最後計算合法的方案數,也就是:\(\sum\limits_{j>k} f[n][j][k]\)(當j>k時,顯然選擇的菜品超出了總數的一半,嗯

時間複雜度是\(O(mn^3)\)的,能夠拿到84pts的分數

考慮優化:

「真·引用」

注意到在不合法狀況的計算過程當中,也就是\(f[i][j][k]\)的轉移過程當中,咱們實際上並不關心j,k的具體數值,而只關心相對的大小關係;因此咱們能夠將狀態變爲\(f[i][j]\),表示前i行,當前L列的數比其餘列的數多了j個,則有轉移:

\(f[i][j]=f[i-1][j]+f[i-1][j-1]*a[i][L]+f[i-1][j+1]*s[i][L]\)

由於多的j多是負數,因而咱們須要將j同時+n這樣若是L列的數比其它列少k的狀況,就能夠看作:\(f[i-1][n-k]\)

不合法狀態最後的答案也就是:

\(\sum\limits_{j=1}^{n}f[n][n+j]\)

那麼總結起來,最後答案應該爲全部方案-不合法方案,即:

\(\sum\limits_{j=1}^ng[n][j]-\sum\limits_{j=1}^nf[n][n+j]\)

記得取模,大概就行了

#include<bits/stdc++.h>
#define ll long long

using namespace std;

inline int read() {
    int ans=0;
    char last=' ',ch=getchar();
    while(ch>'9'||ch<'0') last=ch,ch=getchar();
    while(ch>='0'&&ch<='9') ans=(ans<<1)+(ans<<3)+ch-'0',ch=getchar();
    if(last=='-') ans=-ans;
    return ans;
}
const int mod=998244353;

int n,m;
int a[110][2019];
int s[110][2019];
ll ans,ANS;
ll f[110][320],g[110][310];

int main() {
    n=read();m=read();
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++) {
            a[i][j]=read()%mod;
            s[i][0]=(s[i][0]+a[i][j])%mod;
        }
    for(int i=1;i<=n;i++) 
        for(int j=1;j<=m;j++) //寫代碼時的s*錯誤之m變n,因而很愉快的掛了9個點(顯然已改正
            s[i][j]=(s[i][0]-a[i][j]+mod)%mod;
    for(int L=1;L<=m;L++) {
        f[0][n]=1;
        for(int i=1;i<=n;i++) 
            for(int j=n-i;j<=n+i;j++) 
                f[i][j]=(f[i-1][j]%mod+(f[i-1][j-1]%mod*a[i][L])%mod+(f[i-1][j+1]%mod*s[i][L])%mod)%mod;
        for(int i=1;i<=n;i++) 
            ans=(ans+f[n][n+i]%mod)%mod;
    }
    g[0][0]=1;
    for(int i=1;i<=n;i++)
        for(int j=0;j<=n;j++) {
            g[i][j]=g[i-1][j];
            if(j>0)
                g[i][j]=(g[i][j]+(g[i-1][j-1]*s[i][0])%mod)%mod;
        }
    for(int i=1;i<=n;i++) 
        ANS=(ANS+g[n][i])%mod;
    printf("%lld",(ANS-ans+mod)%mod);
    return 0;
}

T2:劃分

在考場上剛開始的時候想到一個很是很是錯的貪心(爲啥說它很是很是錯呢,由於我連第一組樣例都過不了

因而果斷刪了(快考完的時候後悔刪掉,應該留着騙分的(總比最後直接平方加起來可能獲得的分多

考場上想了dp,而後由於dp巨菜,\(O(n^3)\)的思路幾近推出,可最後仍是沒有推出(枯了

果斷dfs滾粗

而後低估了本身dfs能夠拿的分,數據分治了n<=50時dfs,拿24pts,但實際好像分能夠拿到28pts(雖然只是多了4分但一分也是分啊

在考場上徹底沒有管type=1的狀況,由於明確知道本身前面的分都拿不全。

聽說這道題卡內存要寫高精(心如止水

果然毒瘤出題人嘛

dfs:

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<cstdlib>
#include<queue>
#include<cmath>
#include<ctime>
#define ll long long

using namespace std;

inline int read() {
    int ans=0;
    char last=' ',ch=getchar();
    while(ch>'9'||ch<'0') last=ch,ch=getchar();
    while(ch>='0'&&ch<='9') ans=(ans<<1)+(ans<<3)+ch-'0',ch=getchar();
    if(last=='-') ans=-ans;
    return ans;
}

int n,type;
int a[500010];
ll sum[500010];
ll ans=214748364720040703;

void dfs(int num,ll now,int Sum) {
    if(num==n) {
        ans=min(ans,now);
        return;
    }
    for(int i=num+1;i<=n;i++) {
        if(sum[i]-sum[num]>=Sum) {
            dfs(i,now+(sum[i]-sum[num])*(sum[i]-sum[num]),sum[i]-sum[num]);
        }
    }
}

int main() {
    n=read();
    type=read();
    if(type==0) {
        for(int i=1;i<=n;i++) {
            a[i]=read();
            sum[i]=sum[i-1]+a[i];
        }
        if(n<=100) {
            dfs(0,0,0);
            printf("%lld",ans);
        }
    }
    return 0;
}

\(O(n^3) \ \ \mathcal{DP} \ \ 48 \ pts\)

問:我爲啥只粘了個代碼???

\(dp[i][j]\)表示當前劃分到i,i的上一個劃分點是j的最優值

因而:

\(dp[i][j]=min(dp[i][j]+dp[j][k]+(sum[i]-sum[j])^2),sum[i]-sum[j]>=sum[j]-sum[k]\)

要注意考慮j=0時,\(dp[i][j]=min(dp[i][j],(sum[i]-sum[j])^2)\)

#include<bits/stdc++.h>
#define ull long long

using namespace std;

inline long long read() {
    long long ans=0;
    char last=' ',ch=getchar();
    while(ch>'9'||ch<'0') last=ch,ch=getchar();
    while(ch>='0'&&ch<='9') ans=(ans<<1)+(ans<<3)+ch-'0',ch=getchar();
    if(last=='-') ans=-ans;
    return ans;
}
ull d(ull x) {return x*x;} 

int n,type;
ull a[4110],sum[4110];
ull dp[4110][4110];

int main() {
    n=read();
    y=read();
    if(y==0) {
        for(int i=1;i<=n;i++) {
            a[i]=read();
            sum[i]=sum[i-1]+a[i];
        }
        ull K=214748364720040703;
        for(int i=0;i<=n;i++)
            for(int j=0;j<=n;j++)
                dp[i][j]=K;
        dp[1][0]=a[1]*a[1];
        for(int i=2;i<=n;i++) {
            for(int j=0;j<i;j++) {
                if(j==0) 
                    dp[i][j]=min(dp[i][j],d(sum[i]-sum[j]));
                for(int k=0;k<j;k++) 
                    if(sum[i]-sum[j]>=sum[j]-sum[k]) 
                        dp[i][j]=min(dp[i][j],dp[j][k]+d(sum[i]-sum[j]));
            }
        }
    }
    ull y=214748364720040703;
    for(int i=0;i<n;i++)
        y=min(y,dp[n][i]);
    printf("%lld",y);
    return 0;
}

\(O(n^2)\ \ \mathcal{DP} \ \ 64 \ pts\)

\(dp[i][0]\)表示最後一個劃分點是i的最優值,\(dp[i][1]\)表示最優值時,i做爲劃分點與它前面的數(固然也能夠是就它本身)所組成的值是多少;

而後就能夠迷惑的算了:

\(\mathbb{A}.\)初始狀態\(dp[0][0]=dp[0][1]=0\),顯然當在第0個數的時候的平方的和與它前面的數都是0;

\(\mathbb{B}.\)考慮如何計算\(dp[i][0]\):

\(\mathfrak{a}.\)首先考慮a[i]是否能夠和前面的某一段數合併成一個數,從而使答案更小:根據某些神奇的貪心想法能夠知道,當最後一段的數越小的時候,咱們的答案是越小的(感性李姐一下,這裏再也不展開贅述,所以假設能夠與前面某一段數合併成一個數,那麼必定是與剛剛咱們記錄的\(dp[i-1][1]\)合併,所以咱們能夠計算出\((dp[i-1][1]+a[i])^2\),而後\(+(dp[i-1][0]-dp[i-1][1]^2)\)(由於以a[i-1]爲結尾的這一段數被劃分給了a[i],所以在+(i-1)這個位置的貢獻時,要把\(dp[i-1][1]\)\(dp[i-1][0]\)的貢獻減去;

\(\mathfrak{b}.\)考慮i是否和之後的某個點j合成一個新的數,相應的對\(dp[j][0]\)作出貢獻:從i~n循環枚舉j,記錄當前a[i]~a[j]的和sum,那麼\(dp[j][0]=min(dp[i-1][0]+sum^2)\),在更新\(dp[j][0]\)的同時更新\(dp[j][1]\);

\(\mathbb{C}.\)因而最後的答案就是\(dp[n][0]\);

總的來講64pts思路的突破點在於想到貪心叭。

#include<bits/stdc++.h>
#define ull unsigned long long

using namespace std;

inline long long read() {
    long long ans=0;
    char last=' ',ch=getchar();
    while(ch>'9'||ch<'0') last=ch,ch=getchar();
    while(ch>='0'&&ch<='9') ans=(ans<<1)+(ans<<3)+ch-'0',ch=getchar();
    if(last=='-') ans=-ans;
    return ans;
}
const ull _yKy=214748364720040703;//← ~~我媳婦~~ 
ull d(ull x) {return x*x;} 

int n,type;
ull a[5010];
ull dp[5010][2];

int main() {
    n=read();
    type=read();
    for(int i=1;i<=n;i++) dp[i][0]=_yKy;
    if(type==0) {
        for(int i=1;i<=n;i++) 
            a[i]=read();
        ull sum=0;
        dp[0][0]=dp[0][1]=0;//A
        for(int i=1;i<=n;i++) {
            //B a:
            ull w1=dp[i-1][0]-d(dp[i-1][1]);
            ull w2=w1+d(dp[i-1][1]+a[i]);
            if(w2<dp[i][0]) {
                dp[i][0]=w2;
                dp[i][1]=a[i]+dp[i-1][1];
            }
            //B b:
            sum=0;
            for(int j=i;j<=n;j++) {
                sum+=a[j];
                if(sum<dp[i-1][1]) continue;
                ull w3=dp[i-1][0]+d(sum);
                if(w3<dp[j][0]) {
                    dp[j][0]=w3;
                    dp[j][1]=sum;
                }
            }
        }
    }
    printf("%lu",dp[n][0]);
    return 0;
}

\(O(n)\ \\)正解

咕咕咕

T3:樹的重心

咕咕咕

相比於T2,好像T3的部分分更容易拿一些,暴力與鏈的部分若是時間充足的話好像均可以打出來,但考場上的lz很是沒有策略的一直也不知道在剛哪一個題,反正T3鏈的部分打MLE了(十分不明白爲啥,而後草草打了暴力還沒寫對(也沒有調就這麼交上去了,因而勝利爆零不爆零的話怎麼着也有10分了吧

\(\mathfrak{Before} \ \ \mathfrak{End}:\)

一些閒言碎語:

\(\mathbb{A}.\)看頭文件就能看出哪些是考場代碼,哪些是回來之後又寫的

\(\mathbb{B}.\)雖然在弱省而且今年由於題難因而壓得分很低,但就我這點破分大概是與省一無緣的(是真的很但願本身今年能拿省一的,雖然功利了些

\(\mathbb{C}.\)考場上作題策略上可能還不夠好叭,對作題順序沒有把握好,並且老是懼怕爆,因而不少很暴力的騙分卻不敢寫(這不符合我寫貪心時只管大膽胡說,毫不當心求證的特色誒

\(\mathbb{D}.\)day1 t1 文件名寫錯成了node,因而day1差點爆零。

​ $\mathbb{E}.$6道題爆了一半的0,格雷碼有被卡的可能,算一算今年原本能夠拿到可是沒拿到的暴力分數得有5+20+10+0+0+25=>55~60分

因而從而由於考CSP從而達成四個星期不回家的成就√

明明很早就該有的東西被我整到均可以申訴了我才發(而且還並無整完,就當我太頹廢了叭

相關文章
相關標籤/搜索