【题解】毒瘤 OI 刷题汇总 [SCOI2013]

【题解】毒瘤 OI 刷题汇总 [SCOI2013]

由于不清楚题目顺序,就按照 \(\text{BZOJ}\) 上面的排列好了。


【Day1 T1】摩托车交易

传送门:摩托车交易 \(\text{[P3280]}\) \(\text{[Bzoj3322]}\)

【题目描述】

给出一个 \(n\) \((n\leqslant 10^5)\) 个点 \(m\) \((m\leqslant 2*10^5)\) 条边的无向连通图,图中的点、边带有边权,其中有 \(q\) \((0\leqslant q\leqslant n)\) 为特殊点,对于任意两个特殊点之间都有一条权值为 \(inf\) 的边。

现需要按照给定顺序依次经过这 \(n\) 个点,在某些点可以购买本子,某些点可以卖出本子,在各点的本子交易量不能超过该点的点权,经过某条边时所携带的本子数量不能超过该边的边权。

在进行每次卖出交易时您想要使得本次的卖出量最大,并且在与最后一个点进行交易后手上不能有剩余的本子。请按照顺序依次输出最大卖出量。

【分析】

最大生成树 + 倍增 \(LCA\) 求路径最小值,模拟本子交易。

注意到我们只需要尽量携带最多的本子,不必在意是否卖光的问题,因为实际行动时可以通过调整数量来避免多余本子的出现。

很显然我们可以先求出这 \(n\) 个点的最大生成树,每次移动时走树上的路径就可以保证本子运送量最大。

处理特殊点显然不能暴力枚举,可以新建一个中转点 \(n+1\),所有特殊点均向 \(n+1\) 连一条权值为 \(inf\) 的边即可(注意细节:新建节点后求生成树就是 \(n+1\) 个节点了,还有就是当不存在特殊点时不能新建中转点,否则会不连通)。

然后随便写个倍增 \(LCA\) 或者树剖求树上路径最小值,设个变量 \(v\) 表示目前携带的本子数量,瞎模拟一下就可以了。

时间复杂度:\(O((m+q)\log(m+q)+n\log n)\)

【Code】

#include<algorithm>
#include<cstring>
#include<cstdio>
#define LL long long
#define Re register LL
using namespace std;
const LL N=1e5+3,M=2e5+3,inf=1e18,logN=17;
LL n,m,x,y,o,T,A[N],ip[N],fa[N],head[N];
struct QAQ{LL w,to,next;}a[N<<1];
inline void add(Re x,Re y,Re z){a[++o].to=y,a[o].w=z,a[o].next=head[x],head[x]=o;}
struct QWQ{LL x,y,z;inline bool operator<(const QWQ &O)const{return z>O.z;}}b[M+N];
inline void in(Re &x){
    int f=0;x=0;char c=getchar();
    while(c<'0'||c>'9')f|=c=='-',c=getchar();
    while(c>='0'&&c<='9')x=(x<<1)+(x<<3)+(c^48),c=getchar();
    x=f?-x:x;
}
inline LL find(Re x){return x==fa[x]?x:fa[x]=find(fa[x]);}
struct LCA{
    LL deep[N],dis[N][20],ant[N][20];
    inline void dfs(Re x,Re fa,Re w){
        deep[x]=deep[ant[x][0]=fa]+1,dis[x][0]=w;
        for(Re i=1;(1<<i)<=deep[x];++i)ant[x][i]=ant[ant[x][i-1]][i-1],dis[x][i]=min(dis[x][i-1],dis[ant[x][i-1]][i-1]);
        for(Re i=head[x];i;i=a[i].next)if(a[i].to!=fa)dfs(a[i].to,x,a[i].w);
    }
    inline LL ask(Re x,Re y){
        if(deep[x]<deep[y])swap(x,y);Re ans=inf;
        for(Re i=logN;i>=0;--i)if(deep[ant[x][i]]>=deep[y])ans=min(ans,dis[x][i]),x=ant[x][i];
        if(x==y)return ans;
        for(Re i=logN;i>=0;--i)if(ant[x][i]!=ant[y][i])ans=min(ans,min(dis[x][i],dis[y][i])),x=ant[x][i],y=ant[y][i];
        return min(ans,min(dis[x][0],dis[y][0]));
    }
}T1;
int main(){
//    freopen("123.txt","r",stdin);
    in(n),in(m),in(T);
    for(Re i=1;i<=n;++i)in(ip[i]);
    for(Re i=1;i<=n;++i)in(A[i]);
    for(Re i=1;i<=m;++i)in(b[i].x),in(b[i].y),in(b[i].z);
    if(T){
        while(T--)in(x),b[++m].x=x,b[m].y=n+1,b[m].z=inf;
        sort(b+1,b+m+1);
        for(Re i=1;i<=n+1;++i)fa[i]=i;
        for(Re i=1,t=0;i<=m&&t<n;++i)
            if((x=find(b[i].x))!=(y=find(b[i].y)))
                ++t,fa[x]=y,add(b[i].x,b[i].y,b[i].z),add(b[i].y,b[i].x,b[i].z);
    }
    else{
        sort(b+1,b+m+1);
        for(Re i=1;i<=n;++i)fa[i]=i;
        for(Re i=1,t=0;i<=m&&t<n-1;++i)
            if((x=find(b[i].x))!=(y=find(b[i].y)))
                ++t,fa[x]=y,add(b[i].x,b[i].y,b[i].z),add(b[i].y,b[i].x,b[i].z);
    }
    T1.dfs(1,0,inf);LL w=1,v=A[ip[1]];
    while(v<0)puts("0"),v=A[ip[++w]];
    for(Re i=w+1,x=ip[w];i<=n;x=ip[i],++i){
        Re to=ip[i];v=min(v,(LL)T1.ask(x,to));
        if(A[to]<0)printf("%lld\n",min(v,(LL)-A[to])),v-=min(v,(LL)-A[to]);
        else v+=A[to];
    }
}

【Day1 T2】多项式的运算

传送门:多项式的运算 \(\text{[P3278]}\) \(\text{[Bzoj3323]}\)

【题目描述】

你需要维护一个关于 \(x\) 的无穷多项式(初始时对于所有的 \(i\) 都有 \(a_i=0\)):\(f(x)=a_0x^0+a_1x^1+a_2x^2...\)

现给出 \(m\) \((m\leqslant 10^5)\) 个操作,操作种类如下 \((0\leqslant L\leqslant R \leqslant 10^5,\) \(T\leqslant 10)\)

  • \(mul\ L\ R\ v:\)\(x^L,x^{L+1}...x^R\) 这些项的系数乘以 \(v\)

  • \(add\ L\ R\ v:\)\(x^L,x^{L+1}...x^R\) 这些项的系数加上 \(v\)

  • \(mulx\ L\ R:\)\(x^L,x^{L+1}...x^R\) 这些项乘以变量 \(x\)

  • \(query\ x:\) 询问 \(f(x)\) 的值(满足本操作只会出现 \(T\) 次)

【分析】

平衡树大水题。

用一颗 \(splay\) 维护 \(a_0,a_1...a_{10^5+1}\) 一共 \(10^5+2\) 个系数,

操作一和操作二就是个小学生懒标记的应用,

操作三可以分为两步:在 \(a_{L-1}\)\(a_{L}\) 中间插入一个 \(0\),然后把 \(a_R\) 累加到 \(a_{R+1}\) 头上。

操作四可以先预处理出 \(x\)\(0\sim 10^5+1\) 次幂,然后 \(dfs\) 中序遍历 \(splay\) 暴力计算答案即可。

时间复杂度:\(O(m\log n+Tn)\),其中 \(n=10^5+2\)

【Code】

#include<algorithm>
#include<cstring>
#include<cstdio>
#include<queue>
#define LL long long
#define Re register int
using namespace std;
const int N=1e5+9,P=20130426;
int x,y,z,n=100002,T,now,ans,X[N];char op[10];
inline void in(Re &x){
    int f=0;x=0;char c=getchar();
    while(c<'0'||c>'9')f|=c=='-',c=getchar();
    while(c>='0'&&c<='9')x=(x<<1)+(x<<3)+(c^48),c=getchar();
    x=f?-x:x;
}
struct Splay{
    #define pl tr[p].ps[0]
    #define pr tr[p].ps[1]
    #define pf tr[p].fa
    #define pv tr[p].v
    int O,root;queue<int>Q;
    struct QAQ{int v,fa,add,mul,size,ps[2];}tr[N];
    inline void pushup(Re p){if(p)tr[p].size=1+tr[pl].size+tr[pr].size;}
    inline void updata1(Re p,Re v){//mul
        tr[p].v=(LL)tr[p].v*v%P,tr[p].mul=(LL)tr[p].mul*v%P,tr[p].add=(LL)tr[p].add*v%P;
    }
    inline void updata2(Re p,Re v){//add
        (tr[p].v+=v)%=P,(tr[p].add+=v)%=P;
    }
    inline void pushdown(Re p){
        if(tr[p].mul!=1){
            if(pl)updata1(pl,tr[p].mul);
            if(pr)updata1(pr,tr[p].mul);
            tr[p].mul=1;
        }
        if(tr[p].add){
            if(pl)updata2(pl,tr[p].add);
            if(pr)updata2(pr,tr[p].add);
            tr[p].add=0;
        }
    }
    inline int which(Re p){return tr[pf].ps[1]==p;}
    inline void connect(Re p,Re fa,Re o){tr[pf=fa].ps[o]=p;}
    inline void rotate(Re p){
        Re fa=pf,fas=which(p);
        Re pa=tr[fa].fa,pas=which(fa);
        Re x=tr[p].ps[fas^1];
        connect(x,fa,fas),connect(fa,p,fas^1),connect(p,pa,pas);
        pushup(fa),pushup(p);
    }
    inline void splay(Re p,Re to){
        for(Re fa;pf!=to;rotate(p))
            if(tr[fa=pf].fa!=to)rotate(which(p)==which(fa)?fa:p);
        if(!to)root=p;
    }
    inline void clear(Re p){pl=pr=pf=pv=tr[p].add=0,tr[p].mul=tr[p].size=1;}
    inline void New(Re &p,Re fa){
        if(Q.empty())p=++O;
        else p=Q.front(),Q.pop();
        clear(p),pf=fa;
    }
    inline void CL(Re p){clear(p),Q.push(p);}
    inline void build(Re &p,Re L,Re R,Re fa){
        if(L>R)return;
        Re mid=L+R>>1;New(p,fa);
        build(pl,L,mid-1,p),build(pr,mid+1,R,p);
        pushup(p);
    }
    inline int find(Re p,Re k){
        pushdown(p);
        if(tr[pl].size>=k)return find(pl,k);
        else return k<=tr[pl].size+1?p:find(pr,k-tr[pl].size-1);
    }
    inline int split(Re L,Re R){
        Re p=find(root,L-1),q=find(root,R+1);
        splay(p,0),splay(q,p);
        return tr[q].ps[0];
    }
    inline void dfs(Re p){
        pushdown(p);
        if(pl)dfs(pl);
        if(now!=-1&&pv)(ans+=(LL)pv*X[now]%P)%=P;
        ++now;
        if(pr)dfs(pr);
    }
    inline void sakura(Re L,Re R){
        Re l=find(root,L-1),r=find(root,R+1);
        splay(l,0),splay(r,l);

        Re p=tr[r].ps[0];
        while(pl)pushdown(p),p=pl;
        pushdown(p),New(pl,p);
        while(p)pushup(p),p=pf;

        p=tr[r].ps[0];
        while(pr)pushdown(p),p=pr;
        pushdown(p),(tr[r].v+=pv)%=P;
        Re fa=pf,fas=which(p);
        if(pl)connect(pl,fa,fas);
        else tr[fa].ps[fas]=0;
        CL(p),p=fa;
        while(p)pushup(p),p=pf;
    }
}TR;
int main(){
//    freopen("123.txt","r",stdin);
    in(T),TR.build(TR.root,1,n+2,0);
    while(T--){
        scanf("%s",op);
        if(op[0]=='q'){
            in(x),ans=0,now=-1,X[0]=1;
            if(!x){puts("0");continue;}
            for(Re i=1;i<n;++i)X[i]=(LL)X[i-1]*x%P;
            TR.dfs(TR.root),printf("%d\n",ans);
        }
        else{
            in(x),in(y),x+=2,y+=2;
            if(op[0]=='a')in(z),TR.updata2(TR.split(x,y),z);
            else if(strlen(op)<4)in(z),TR.updata1(TR.split(x,y),z);
            else TR.sakura(x,y);
        }
    }
}

【Day1 T3】火柴棍数字

传送门:火柴棍数字 \(\text{[P3283]}\) \(\text{[Bzoj3324]}\)

【题目描述】

给出一个长度为 \(n\) \((n\leqslant 500)\) 的十进制数 \(s\),现用一堆火柴棍摆成了这个数,您最多可以移动 \(m\) \((m\leqslant 3500)\) 根木棍,求最大能使这个数 \(s\) 变得多大。

【分析】

神仙 \(dp\)

注意到最优方案一定是在最左边不断地放 \(1\),如果可操作木棍为奇数,则用剩下的一根把最左边的那个 \(1\) 变成 \(7\)

先预处理一个转移数组 \(val\),其中 \(val[i][j]\) 表示将数字 \(i\) 变为 \(j\) 所需要的移动次数。

\(cnt[i]\) 表示组成数字 \(i\) 所需木棍数量,则将数字 \(i\) 转换为 \(j\) 后所得到的空闲木棍数量就是 \(cnt[i]-cnt[j]\) 。这里的 “空闲木棍” 指的是不需要移动代价(移动代价的计算被放到了 \(val\) 里面),可以随便放的木棍,下同。

\(dp[i][j]\) 表示目前从右至左枚举到第 \(i\) 位,手里有 \(j\) 个空闲木根,所需要的最小移动木棍次数,

考虑枚举当前位数字 \(s_i\) 的转移目标 \(k\),设 \(J=j-(cnt[s_i]-cnt[k])\),则有 \(dp[i][j]=\min\{dp[i+1][J]+val[s_i][k]]\}\) \((0\leqslant J \leqslant m)\)

注意输出答案时要满足左边的尽量大,所以不能直接记录每次转移的最优决策点(因为这个“最优”是针对移动次数最小而言),而应该写一个 \(dfs\) 从左至右枚举,每次在所有合法决策点中找到转移目标数字最大的那一个(由于寻找的是合法决策点,所以能保证最后火柴棍的使用一定合法)。发现在这个过程中 \(j\) 可能为负数(因为是倒着枚举),用递推转移写起来很不方便,可以把前面 \(dp\) 的转移也换成 \(dfs\)

时间复杂度:\(O(nm)\)

【Code】

(貌似应该把数组第二维开两倍处理负数的情况,但数据水直接过了,懒得去管这些细节了

#include<algorithm>
#include<cstring>
#include<cstdio>
#define LL long long
#define Re register int
using namespace std;
const int N=503,M=3503,inf=1e9;
int val[10][10]={
    {0,4,2,2,3,2,1,3,0,1},
    {0,0,2,0,0,1,1,0,0,0},
    {1,4,0,1,3,2,1,3,0,1},
    {1,3,1,0,2,1,1,2,0,0},
    {1,2,2,1,0,1,1,2,0,0},
    {1,4,2,1,2,0,0,3,0,0},
    {1,5,2,2,3,1,0,4,0,1},
    {0,1,1,0,1,1,1,0,0,0},
    {1,5,2,2,3,2,1,4,0,1},
    {1,4,2,1,2,1,1,3,0,0},
};
int cnt[10]={6,2,5,5,4,5,6,3,7,6};
int n,m,dp[N][M],vis[N][M];char s[N];
//dp[i][j]: 目前扫描到第i位,手里有j个空闲木根可以随意操作,所需要的最小移动木棍次数
inline void in(Re &x){
    Re f=0;x=0;char c=getchar();
    while(c<'0'||c>'9')f|=c=='-',c=getchar();
    while(c>='0'&&c<='9')x=(x<<1)+(x<<3)+(c^48),c=getchar();
    x=f?-x:x;
}
inline int dfs(Re i,Re j){
    if(j>m)return inf;
    if(i>n)return j?inf:0;
    if(vis[i][j])return dp[i][j];
    vis[i][j]=1,dp[i][j]=inf;
    for(Re k=9,j_,tmp;k>=0;--k)
        if((j_=j+cnt[k]-cnt[s[i]-'0'])>=0&&j_<=m)
            if((tmp=dfs(i+1,j_)+val[s[i]-'0'][k])<dp[i][j])
                if(tmp<=m)dp[i][j]=tmp;
    return dp[i][j];
}
inline void print(Re i,Re j){
    if(i>n)return;
    for(Re k=9,j_;k>=0;--k)
        if(dfs(i+1,j_=j+cnt[k]-cnt[s[i]-'0'])+val[s[i]-'0'][k]<=m){
            printf("%d",k),m-=val[s[i]-'0'][k],print(i+1,j_);return;
        }
}
int main(){
//    freopen("123.txt","r",stdin);
    scanf("%s%d",s+1,&m),n=strlen(s+1);
    for(Re j=m;j>1;--j)//枚举手中的空闲木棍数量
        if(dfs(1,j)<=m){
            Re now=j;
            if(now&1)putchar('7'),now-=3;
            while(now)putchar('1'),now-=2;
            print(1,j);return 0;
        }
    print(1,0);//如果不足2个就不能在前面补数,因此答案为print(1,0)而不能是print(1,1)
}

【Day2 T1】密码

传送门:密码 \(\text{[P3279]}\) \(\text{[Bzoj3325]}\)

【题目描述】

已知某长为 \(n (n\leqslant 10^5)\) 的字符串以每个位置/空隙为中心的最长回文串长度,现需构造一个字典序最小的合法字符串。

【分析】

详情见另一篇博客:【题解】密码 \(\text{[SCOI2013] [P3279]}\)

【Code】

不知道这儿能放啥,干脆买个萌吧(⊙ω⊙)

【Day2 T2】数数

传送门:数数 \(\text{[P3281]}\) \(\text{[Bzoj3326]}\)

【题目描述】

略。

【分析】

毒瘤数位 \(dp\),题解始终没看懂,先咕着。

【Code】

不知道这儿能放啥,干脆买个萌吧(⊙ω⊙)

【Day2 T3】泡泡鱼

传送门:泡泡鱼 \(\text{[P3282]}\) \(\text{[Bzoj3327]}\)

【题目描述】

【分析】

毒瘤 \(OI\) 又在出毒瘤计算几何了,题倒是不难,但比较麻烦,不想写。

【Code】

不知道这儿能放啥,干脆买个萌吧(⊙ω⊙)

posted @ 2020-04-16 11:25  辰星凌  阅读(270)  评论(0编辑  收藏  举报