【考试反思】联赛模拟测试12

震惊,我居然不会数细胞。

菜的离谱。

T1: 100 \(\rightarrow\) 80

T4: 20 \(\rightarrow\) 0

T1:松鼠的新家

震惊,我建边居然不开二倍,那没事了。

树上差分裸题。

Code
#include <bits/stdc++.h>
using namespace std;
const int maxn=3e5+10;
int n;
int a[maxn];

struct Edge{
    int from,to,nxt;
}e[maxn];//这样直接80 = =

inline int read(){
    int x=0;bool fopt=1;char ch=getchar();
    for(;!isdigit(ch);ch=getchar())if(ch=='-')fopt=0;
    for(;isdigit(ch);ch=getchar())x=(x<<3)+(x<<1)+ch-48;
    return fopt?x:-x;
}

int head[maxn],cnt;
inline int add(int u,int v){
    e[++cnt].from=u;
    e[cnt].to=v;
    e[cnt].nxt=head[u];
    head[u]=cnt;
}

int fa[maxn],dep[maxn],siz[maxn],son[maxn];
void dfs1(int u){
    dep[u]=dep[fa[u]]+1;siz[u]=1;
    for(int i=head[u];i;i=e[i].nxt){
        int v=e[i].to;
        if(v==fa[u])continue;
        fa[v]=u;dfs1(v);
        siz[u]+=siz[v];
        if(!son[u]||siz[v]>siz[son[u]])son[u]=v;
    }
}

int top[maxn];
void dfs2(int u,int t){
    top[u]=t;
    for(int i=head[u];i;i=e[i].nxt){
        int v=e[i].to;
        if(v==fa[u])continue;
        dfs2(v,v==son[u]?t:v);
    }
}

inline int lca(int u,int v){
    while(top[u]!=top[v]){
        if(dep[top[u]]<dep[top[v]])swap(u,v);
        u=fa[top[u]];
    }
    return dep[u]<dep[v]?u:v;
}

int c[maxn];
long long f[maxn];
void dfs3(int u){
    f[u]=c[u];
    for(int i=head[u];i;i=e[i].nxt){
        int v=e[i].to;
        if(v==fa[u])continue;
        dfs3(v);
        f[u]+=f[v];
    }
}

int main(){
#ifndef LOCAL
    freopen("home.in","r",stdin);
    freopen("home.out","w",stdout);
#endif
    n=read();
    for(int i=1;i<=n;i++)
        a[i]=read();
    for(int i=1;i<n;i++){
        int u=read(),v=read();
        add(u,v);add(v,u);
    }
    dfs1(1);dfs2(1,1);
    for(int i=1;i<n;i++){
        int u=a[i],v=a[i+1],lcam=lca(u,v);
        c[u]++;c[v]++;c[lcam]--;
        if(fa[lcam])c[fa[lcam]]--;
    }
    dfs3(1);
    for(int i=2;i<=n;i++)
        f[a[i]]--;
    for(int i=1;i<=n;i++)
        printf("%lld\n",f[i]);
    return 0;
}

谢谢,已经在学了

T2:trade

震惊,贪心题我居然 DP,直接挂掉。

震惊,第二维限制到 500 之后直接 AC。

震惊,天皇震怒直接卡掉

乱搞的 Code
#include <bits/stdc++.h>
using namespace std;
const int maxn=1e5+10;
int n;
int a[maxn];
int f[2][maxn];

inline int read(){
    int x=0;bool fopt=1;char ch=getchar();
    for(;!isdigit(ch);ch=getchar())if(ch=='-')fopt=0;
    for(;isdigit(ch);ch=getchar())x=(x<<3)+(x<<1)+ch-48;
    return fopt?x:-x;
}

int main(){
#ifndef LOCAL
    freopen("trade.in","r",stdin);
    freopen("trade.out","w",stdout);
#endif
    n=read();
    for(int i=1;i<=n;i++)
        a[i]=read();
    memset(f,-0x3f,sizeof(f));
    f[0][0]=0;
    int k=0;
    for(register int i=1;i<=n;i++){
        k^=1;
        for(register int j=0;j<min(i,500);j++){
            f[k][j]=max(f[k][j],f[k^1][j]);
            f[k][j+1]=max(f[k][j+1],f[k^1][j]-a[i]);
            if(j)f[k][j-1]=max(f[k][j-1],f[k^1][j]+a[i]);
        }
    }
    printf("%d\n",f[k][0]);
    return 0;
}

所以正解居然是反悔贪心。

可能大家都能想到贪心,那就是从后选最大的,从前选最小的。那么如何维护?

用一个小根堆来维护所有的 \(a[i]\),如果队列为空或当前的 \(a[i]\) 小于等于堆顶,直接入队。否则,买入堆顶并在现在卖出,弹出堆顶,压入当前的两个货物。

由于我们使用小根堆,所以当前堆顶一定是之前最小的买入价值。所以我们买入堆顶的策略肯定是最优的。但在今天卖出却不一定是最优的,因为之后可能出现更大的 \(a[i]\)。所以我们要用可撤销贪心。

假设我们之后会用一次撤销操作,那么设堆顶的货物价值为 \(a\),今天卖出的价值为 \(b\),之后最优日卖出的价值为 \(c\)。因为我们之前取了 \(b-a\) 作价值,然而事实上最优策略是 \(c-a\)。所以我们只需要再取一个 \(c-b\) 作价值即可。这就是压入的第一个货物。至于第二个货物,由于实际上当前策略由 \(b-a\) 转换成了 \(c-a\) 了,所以在价值为 \(b\) 的那天就不必卖东西了,那么就可以选择在当日购入物品了,第二个货物就对应这个物品。

更加严谨的证明

正确的 Code
#include <bits/stdc++.h>
using namespace std;
const int maxn=1e5+10;
int n;
long long ans;

inline int read(){
    int x=0;bool fopt=1;char ch=getchar();
    for(;!isdigit(ch);ch=getchar())if(ch=='-')fopt=0;
    for(;isdigit(ch);ch=getchar())x=(x<<3)+(x<<1)+ch-48;
    return fopt?x:-x;
}

priority_queue<int,vector<int>,greater<int> > q;

int main(){
#ifndef LOCAL
    freopen("trade.in","r",stdin);
    freopen("trade.out","w",stdout);
#endif
    n=read();
    for(int i=1;i<=n;i++){
        int a=read();
        if(q.empty()||a<=q.top())q.push(a);
        else{
            ans+=a-q.top();q.pop();
            q.push(a);q.push(a);
        }
    }
    printf("%lld\n",ans);
    return 0;
}

T3:sum

震惊,这题居然是莫队。

其实前面的两个特殊性质都是提示。既然要用莫队,我们就看 \(S_n^m\) 如何转移。

\(n\) 相等时,显然:

\[S_n^m=S_n^{m-1}+C_n^m \]

\[S_n^{m-1}=S_n^m-C_n^m \]

\(m\) 相等时,我们可以画一个杨辉三角。例如:

  1 3 3
1 4 6

设下面一行就是第 \(n\) 行,上面一行是 \(n-1\) 行,我们都取 \(m\) 个。那么依据小学知识可知 1=14=1+36=3+3。我们发现,在 \(n-1\) 行转移到 \(n\) 行的过程中,除了第 \(m\) 项,其他的都转移了两次。那么我们显然可以得到:

\[S_n^m=2\times S_{n-1}^m-C_{n-1}^m \]

\[S_{n-1}^m=\cfrac{S_n^m+C_{n-1}^m}{2} \]

由于题目可离线,利用这四个 \(O(1)\) 转移的式子,我们可以利用莫队解决问题。注意由于 \(n\geq m\) ,所以 \(m\) 为左端点,\(n\) 为右端点(震惊,好像反过来也可以)。块长取 \(\sqrt{\max n}\)

Code
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int Mod=1e9+7;
const int maxn=1e5+10;
int Max,T,S,ans=1;//ans别初始化成0了...
int res[maxn];

struct Node{
    int n,m,id;
    friend inline bool operator <(register const Node& A,register const Node& B){
        return (A.m/S)^(B.m/S)?A.m<B.m:(((A.m/S)&1)?A.n<B.n:A.n>B.n);
    }
}q[maxn];

inline int read(){
    int x=0;bool fopt=1;char ch=getchar();
    for(;!isdigit(ch);ch=getchar())if(ch=='-')fopt=0;
    for(;isdigit(ch);ch=getchar())x=(x<<3)+(x<<1)+ch-48;
    return fopt?x:-x;
}

int fac[maxn],inv[maxn];
inline void Init(){
    fac[0]=fac[1]=inv[0]=inv[1]=1;
    for(int i=2;i<=1e5;i++)
        inv[i]=(Mod-Mod/i)*inv[Mod%i]%Mod;
    for(int i=2;i<=1e5;i++){
        fac[i]=fac[i-1]*i%Mod;
        inv[i]=inv[i-1]*inv[i]%Mod;
    }
}

inline int C(int n,int m){
    if(n<m)return 0;
    if(!m||n==m)return 1;
    return fac[n]*inv[m]%Mod*inv[n-m]%Mod;
}

signed main(){
#ifndef LOCAL
    freopen("sum.in","r",stdin);
    freopen("sum.out","w",stdout);
#endif
    read();Init();
    T=read();
    for(int i=1;i<=T;i++){
        int n=read(),m=read();
        if(n>Max)Max=n;
        q[i]=(Node){n,m,i};
    }
    S=sqrt(Max);
    sort(q+1,q+T+1);
    int m=1,n=0;//莫队左端点建议从1开始,否则有的题会挂,比如小Z的袜子
    for(int i=1;i<=T;i++){
        int nowm=q[i].m,nown=q[i].n;
        while(n<nown)ans=(2*ans%Mod-C(n++,m)+Mod)%Mod;
        while(m>nowm)ans=(ans-C(n,m--)+Mod)%Mod;
        while(n>nown)ans=(ans+C(--n,m))%Mod*inv[2]%Mod;
        while(m<nowm)ans=(ans+C(n,++m))%Mod;
        res[q[i].id]=ans;
    }
    for(int i=1;i<=T;i++)
        printf("%lld\n",res[i]);
    return 0;
}

T4:building

震惊,我居然不会数细胞。已经开始学队列和栈了。

神奇模拟,正在努力。

posted @ 2020-10-09 11:35  Midoria7  阅读(179)  评论(5编辑  收藏  举报