[比赛题解]CWOI2019-1

[比赛题解]CWOI2019-1

比赛日期:2019.10.12

T1

一道神仙DP题。

我们考虑\(dp[i][j][k]\)表示最后\(i\)位数,\(i-1\)位都是9,最后一位为\(j\),最后\(i\)位数以前的部分最大数为\(k\)时,把最后\(i\)位数减到负数最小需要多少次。\(re[i][j][k]\)代表这个状态下,把这个数减到负数时,负数+10的值。

那么每次操作就会减去\(min(最后i位数中最小值,k)\)。

我们先预处理出\(dp\)和\(re\)数组(从低位向高位递推)。

然后读入给的数,考虑把除1之外所有位数变成9。那么从第二位向高位枚举,每次把当前位减掉1,直到减到9(最后一次注意退位),同时记录在这个过程中走了多少步。因为对于一个前缀全为9,最后一位无论是什么,减去一个小于10的数得到的数的前缀还是全为9。

全部变成9之后即可从高位到低位统计答案即可。

貌似状态设置成“全是9”也可以,但是没试过。

#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<cmath>
using namespace std;
long long dp[20][10][10],res[20][10][10];
long long ans=0;
char s[20];
int num[20];
void init() {
    for(int i=0;i<=9;i++) {
        for(int j=0;j<=9;j++) {
            if(i<j) {//当前位较小
                dp[1][i][j]=1;
                res[1][i][j]=i-j+10;
            }
            else {//当前位较大
                dp[1][i][j]=2;
                res[1][i][j]=10-j;
            }
        }
    }
    for(int i=2;i<=18;i++) {
        for(int j=0;j<=9;j++) {
            for(int k=0;k<=9;k++) {
                res[i][j][k]=j;
                for(int n=9;n>=0;n--) {
                    dp[i][j][k]+=dp[i-1][res[i][j][k]][max(n,k)];
                    res[i][j][k]=res[i-1][res[i][j][k]][max(n,k)];
                }
            }
        }
    }

}
void just_do_it() {
    scanf("%s",s+1);int len=strlen(s+1);
    for(int i=1;i<=len;i++)num[i]=s[i]-'0';
    reverse(num+1,num+1+len);
    int now=num[1];
    for(int i=2;i<=len;i++) {
        while(num[i]!=9) {
            int ma=0;
            for(int j=i;j<=len;j++)ma=max(ma,num[j]);
            if(!ma)break;//无法退位
            num[i]--;
            for(int j=i;num[j]<0;j++) {//退位
                num[j]=9;num[j+1]--;
            }
            ans+=dp[i-1][now][ma];
            now=res[i-1][now][ma];
        }
    }
    for(int i=len;i>=2;i--) {
        while(num[i]) {
            ans+=dp[i-1][now][num[i]];
            now=res[i-1][now][num[i]];
            num[i]--;
        }
    }
    if(now)ans++;
    printf("%lld",ans);
}
int main() {
    init();
    just_do_it();
}

T2

我们考虑三种情况

  1. 被覆盖点为祖先-后代关系
  2. 被覆盖点没有祖先-后代关系
  3. 被覆盖点相等

这三类都可以转换为统计某些点的子树内点的个数。(比如说第二种情况就是统计路径两端分别在\(u,v\)子树内的路径条数)。对于一个端点,我们可以把他转换为\(dfs\)序在一定范围内的点,而对于一条路径,我们可以把它转换成矩阵坐标在一定范围内的点。然后扫描线二维数点就好了。

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<vector>
#define int long long
#define ll long long
using namespace std;
const int maxn=1e5+1000;
int root[maxn],ls[maxn*80],rs[maxn*80],head[maxn],cnt,idx,n,m,dep[maxn],po[maxn],f[maxn][23],size[maxn],dfn[maxn],dfn_cnt;
ll ans1=0,ans2,ans3,sum[maxn*80];
vector<int>list[maxn];
ll gcd(ll a,ll b){return b?gcd(b,a%b):a;}
struct gg {
    int u,v,next;
}side[maxn*2];
//pair<int,int>ori[maxn];
struct data {
    int first,second;
}ori[maxn];
void insert(int u,int v){
    struct gg add={u,v,head[u]};side[++cnt]=add;head[u]=cnt;
}
void dfs(int now,int fa) {
    dep[now]=dep[fa]+1;size[now]=1;dfn[now]=++dfn_cnt;
    f[now][0]=fa;for(int i=1;i<=22;i++)f[now][i]=f[f[now][i-1]][i-1];
    for(int i=head[now];i;i=side[i].next) {
        int v=side[i].v;if(v==fa)continue;
        dfs(v,now);size[now]+=size[v];
    }
}
int lca(int u,int v) {
    if(dep[u]<dep[v])swap(u,v);
    for(int i=22;i>=0;i--)if(dep[f[u][i]]>=dep[v])u=f[u][i];
    if(u==v)return u;
    for(int i=22;i>=0;i--)if(f[u][i]!=f[v][i]){u=f[u][i],v=f[v][i];}
    return f[u][0];
}
int build(int l,int r) {//主席数,版本表示u的范围,线段树表示v的范围
    int now=++idx;
    if(l==r)return now;
    int mid=(l+r)>>1;
    ls[now]=build(l,mid);
    rs[now]=build(mid+1,r);
    return now;
}
bool cop(data x,data y) {
    return x.first<y.first;
}
int insert(int sync,int l,int r,int tar) {
    int now=++idx;
    ls[now]=ls[sync],rs[now]=rs[sync],sum[now]=sum[sync]+1;
    if(l==r)return now;
    int mid=(l+r)>>1;
    if(tar<=mid) {
        ls[now]=insert(ls[sync],l,mid,tar);
    }
    else {
        rs[now]=insert(rs[sync],mid+1,r,tar);
    }
    return now;
}
ll query(int sy1,int sy2,int l,int r,int tl,int tr) {
    ll tot=sum[sy2]-sum[sy1];
    if(tl<=l&&r<=tr)return tot;
    tot=0;int mid=(l+r)>>1;
    if(tl<=mid) {
        tot+=query(ls[sy1],ls[sy2],l,mid,tl,tr);
    }
    if(tr>=mid+1) {
        tot+=query(rs[sy1],rs[sy2],mid+1,r,tl,tr);
    }
    return tot;
}
int jump(int l,int h) {
    for(int i=22;i>=0;i--)if(dep[f[l][i]]>dep[h])l=f[l][i];
    return l;
}
signed main() {
    scanf("%lld%lld",&n,&m);
    for(int i=1;i<n;i++){int u,v;scanf("%lld%lld",&u,&v);insert(u,v);insert(v,u);}
    dfs(1,0);
    for(int i=1;i<=m;i++) {
        scanf("%lld%lld",&ori[i].first,&ori[i].second);
        if(dep[ori[i].first]>dep[ori[i].second])swap(ori[i].first,ori[i].second);
        if(ori[i].first==ori[i].second)po[ori[i].first]++;
        list[dfn[ori[i].first]].push_back(dfn[ori[i].second]);
        list[dfn[ori[i].second]].push_back(dfn[ori[i].first]);
    }
    root[0]=build(1,m);sort(ori+1,ori+1+m,cop);
    for(int i=1;i<=n;i++) {
        root[i]=root[i-1];
        for(int j=0;j<list[i].size();j++)root[i]=insert(root[i],1,n,list[i][j]);
    }

    //for(int i=1;i<=n;i++)
    for(int i=1;i<=m;i++) {
        int u=ori[i].first,v=ori[i].second;//查询u,v被多少路径包含

        //int lu,lv,ru,rv;
        if(lca(u,v)==u) {//祖先-儿子关系
            int p=jump(v,u);
            ans1+=query(root[0],root[dfn[p]-1],1,n,dfn[v],dfn[v]+size[v]-1);
            ans1+=query(root[dfn[p]+size[p]-1],root[n],1,n,dfn[v],dfn[v]+size[v]-1);
        }
        else {
            ans1+=query(root[dfn[u]-1],root[dfn[u]+size[u]-1],1,n,dfn[v],dfn[v]+size[v]-1);
           // ans1+=query(root[dfn[v]-1],root[dfn[v]+size[v]-1],1,n,dfn[u],dfn[u]+size[u]-1);
        }
        ans1--;
    }
    ans2=m*(m-1)/2;ans3=gcd(ans1,ans2);
    cout<<ans1/ans3<<'/'<<ans2/ans3;
    return 0;
}

                    

原文地址:https://www.cnblogs.com/GavinZheng/p/11693176.html

时间: 2024-07-30 18:01:31

[比赛题解]CWOI2019-1的相关文章

【codeforces】【比赛题解】#849 CF Round #431 (Div.2)

cf的比赛越来越有难度了--至少我做起来是这样. 先看看题目吧:点我. 这次比赛是北京时间21:35开始的,算是比较良心. [A]奇数与结束 "奇数从哪里开始,又在哪里结束?梦想从何处起航,它们又是否会破灭呢?" 给定一个长度为n的序列.确定能不能将序列分成奇数个长度为奇数的非空字串,而且这其中每个子串以奇数开头,以奇数结尾.可以只分成一个(1也是奇数). 输入 第一行一个正整数n,表示序列长度. 第二行n个整数,表示序列中的元素. 输出 输出"Yes"或"

【学长出题】【比赛题解】17-09-29

此次比赛由dalao学长@FallDream出题,欢迎查看他的blog! [T1] 题意: 样例: (1<=n<=100000, 1<=xi,yi<=10^9) 题解: 加一个骨牌,就相当于把最后的若干个骨牌删除,再算之前的答案. 如果我们对任意的前i个骨牌算出答案,就可以简单地算出最终答案. 就有了dp的想法,可以发现是比较简单的. f[i]=f[k]+(i-k),k是i推倒后最后一个没有被推倒的骨牌编号. 1 #include<cstdio> 2 #include&

【codeforces】【比赛题解】#854 CF Round #433 (Div.2)

cf一如既往挺丧 看丧题点我! [A]分数 Petya是数学迷,特别是有关于分数的数学.最近他学了所谓一个分数被叫做"真分数"当且仅当其分子小于分母,而一个分数被叫做"最简分数"当且仅当其分子分母互质.在闲暇时间,Petya在用计算器研究:如何把最简真分数转换为小数等问题.有一天他不小心把除号(÷)按成了加号(+),导致他得到了分子与分母的和.Petya想要得到他原来的分数,但他很快发现这不是唯一的.所以现在他想要知道最大的最简真分数使得其分子与分母的和为n. 输入

【比赛题解】03-25生日赛

03-25是小兔的生日-- 这一周是我出题--三道题的难度是 T1<T2<T3 [T1]过生日 其中3<=n,m<=1500 [T2]方程狂魔 其实质数个数是664579个-- [T3]监视 求图的最小生成树,然后天天爱跑步就好.为什么是最小生成树?因为每个小伙伴只会走路径上最大权最小的.先考虑走的边一定是树边:把这个图MST之后,对于一条树上路径,把路径换成另一条非树上路径,结果一定不会更优.因为另一条路径上的最大权值一定没有当前路径上的最大权值更大.而又因为树上权值互异,所以M

【codeforces】【比赛题解】#915 Educational CF Round 36

虽然最近打了很多场CF,也涨了很多分,但是好久没写CF的题解了. 前几次刚刚紫名的CF,太伤感情了,一下子就掉下来了,不懂你们Div.1. 珂学的那场我只做了第一题--悲伤. 这次的Educational Round打的还可以,虽然吧没有涨分(因为我是紫色的啊). 做了前4题,后面3题也比较简单,陆续也做完了. 所以心情好,来写一篇题解! [A]花园 题意: 长度为\(k\)的线段,用若干个长度为\(a_i\)的线段,正好覆盖.(\(a_i|k\)) 给定\(n\)个\(a_i\),求出最小的\

瑞安市信息学竞赛复赛前练习2 1754 Problem D看比赛 题解

问题 D: 看比赛 时间限制: 1 Sec  内存限制: 128 MB 题目描述 比赛开始了,KK 准备记录下每一个重要的事件:红黄牌和进球. 假设只有A 队和B队,每队的号码是1~11 号. 进球事件的表示:Goal A 表示A 进球,Goal B 相反 红牌事件:RedCard A 1表示A队1 号被红牌罚下 黄牌事件:YellowCard B 2表示B队2号被黄牌警告 注意,一个人如果被警告两次就将被罚下(相当于一张红牌). 现在,要输出的是两队的比分.被罚下的名单和被警告的名单. 输入

CHDU2014迎新杯比赛题解

A.神奇彩带 题意:给两个字符串 S 和 T ,让找一个最长的字符串 P 使得 P 是 S 的前缀且是 T 的后缀. 思路:考虑 KMP 算法中的 Next 数组即为所求.只需要在 S 和 T 之间用一个无效的字符连接起来,求其 Next 数组,Next[len] 即为答案. 推荐学习链接: 从头到尾彻底理解 KMP 算法 1 #include <iostream> 2 #include <stdio.h> 3 #include <stdlib.h> 4 #includ

2016年山东省acm比赛题解(差l和h)

a Julyed 题意:n个单词,每天最多背m个单词,最少背多少天 分析:水题,不解释 #include<bits/stdc++.h> using namespace std; int main(){ int t,n,m; scanf("%d",&t); while(t--){ scanf("%d%d",&n,&m); int ans=n/m; if(n%m!=0) ans++; printf("%d\n",a

2019年GPLT L2-1 特立独行的幸福 比赛题解 中国高校计算机大赛-团体程序设计天梯赛题解

对一个十进制数的各位数字做一次平方和,称作一次迭代.如果一个十进制数能通过若干次迭代得到 1,就称该数为幸福数.1 是一个幸福数.此外,例如 19 经过 1 次迭代得到 82,2 次迭代后得到 68,3 次迭代后得到 100,最后得到 1.则 19 就是幸福数.显然,在一个幸福数迭代到 1 的过程中经过的数字都是幸福数,它们的幸福是依附于初始数字的.例如 82.68.100 的幸福是依附于 19 的.而一个特立独行的幸福数,是在一个有限的区间内不依附于任何其它数字的:其独立性就是依附于它的的幸福