SMU Summer 2024 Contest Round 8

[ABC221F] Diameter set - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

思路:树的直径,中心点。

关键点1:
分奇偶讨论:中心点为边的情况要以中心边左右两个点分为两个集合。
中心点为一个点的情况,会出现多个集合,要以"和中心点直接相连的点"分为不同的集合!!!
关键点2:怎么计算答案,这又用到了数学的知识.
当把点分为大小不同的集合之后,就是怎么选点,组合出不同的答案了
首先中心点为边的情况很明显,只能选两个点,那么就是乘法原理:左集合大小乘右集合大小.
当中心点为一个点时,假设有四个集合,大小为:3,3,3,6.
那么怎么计算选两个点的答案贡献?选三个点的答案贡献?选四个点的答案贡献?只有四个集合,最多只能选四个点,每个集合单次只能选一个点。
每个集合可选的情况为点的个数+1,why? 因为给每个集合加了一个0点,选了0点意味着不选这个集合.
那么根据乘法原理:有所有组合情况(3+1)*(3+1)*(3+1)*(6+1)
但是题目说至少要选两个点,那么上面算的情况是多算了的,多算了多少呢?
只选了一个点的情况是不合法的,一共有3+3+3+6种(x,0,0,0),因为每个点都可以作为那个只选的点x.
还要再减去1,因为什么都不选(0,0,0,0)也被算上了,这是不合法的.
所以这种情况的答案为(s1+1)*(s2+1)*(s3+1)*(s4+1)-(s1+s2+s3+s4)-1.
const int mod=998244353;
int n;
vector<int> vct[200005];
int S,T,d,heart1,heart2;  其中一条直径的始点和终点,以及直径的一半距离,以及两个中心点
int maxn=0,curnode;
int road[200005],dis[200005];
vector<int> ans;
void dfs1(int s,int depth,int fa){   找到其中一条直径的起点和终点  o(n)
    if(depth>maxn) maxn=depth,curnode=s;
    for(auto v:vct[s]) if(v!=fa) dfs1(v,depth+1,s);
}
void bfs1(int s){    确定直径的路径,通过路径可以得知中心点.    o(n)
    queue<int> que;
    que.emplace(s);
    while(que.size()){
        int from=que.front();
        que.pop();
        if(from==T) break;
        for(auto v:vct[from]){
            int to=v;
            if(dis[to]==0&&to!=s){
                dis[to]=dis[from]+1;
                que.emplace(to);
                road[to]=from;
            }
        }
    }
}
int dfs2(int s,int depth,int fa){          处理一个中心点的情况,o(n)
    特别留意d==1的情况,图太小了,没有按照所想那样有个间接点,直接就是点本身了.
    if(d==1) return 1;  再写一行
    if(depth==d-1) return (int)vct[s].size()-1;
    int res=0;
    for(auto v:vct[s]) if(v!=fa) res+=dfs2(v,depth+1,s);
    return res;
}
int dfs3(int s,int depth,int fa){    处理两个中心点的情况,o(n)
    if(depth==d-1) return vct[s].size()-1;
    int res=0;
    for(auto v:vct[s]) if(v!=fa) res+=dfs2(v,depth+1,s);
    return res;
}
[ABC221F] Diameter set
https://www.luogu.com.cn/problem/AT_abc221_f
关键点1:
分奇偶讨论:中心点为边的情况要以中心边左右两个点分为两个集合。
中心点为一个点的情况,会出现多个集合,要以"和中心点直接相连的点"分为不同的集合!!!
关键点2:怎么计算答案,这又用到了数学的知识.
当把点分为大小不同的集合之后,就是怎么选点,组合出不同的答案了
首先中心点为边的情况很明显,只能选两个点,那么就是乘法原理:左集合大小乘右集合大小.
当中心点为一个点时,假设有四个集合,大小为:3,3,3,6.
那么怎么计算选两个点的答案贡献?选三个点的答案贡献?选四个点的答案贡献?只有四个集合,最多只能选四个点。
每个集合可选的情况为点的个数+1,why? 因为给每个集合加了一个0点,选了0点意味着不选这个集合.
那么根据乘法原理:有所有组合情况(3+1)*(3+1)*(3+1)*(6+1)
但是题目说至少要选两个点,那么上面算的情况是多算了的,多算了多少呢?
只选了一个点的情况是不合法的,一共有3+3+3+6种(x,0,0,0),因为每个点都可以作为那个只选的点x.
还要再减去1,因为什么都不选(0,0,0,0)也被算上了,这是不合法的.
所以这种情况的答案为(s1+1)*(s2+1)*(s3+1)*(s4+1)-(s1+s2+s3+s4)-1.
void solve(){                       o(n)
    cin>>n;
    for(int i=1;i<=n-1;i++){
        int u,v; cin>>u>>v;
        vct[u].emplace_back(v);
        vct[v].emplace_back(u);
    }
    if(n==2) { 特判一下小情况
        cout<<"1";
        return;
    }
    maxn=0,dfs1(1,0,0),S=curnode;
    maxn=0,dfs1(S,0,0),T=curnode;
    bfs1(S);
    树的直径长度奇偶不同,情况不同,分奇偶讨论
    if(dis[T]&1){   中心为两个点加中间一条边
        int cur=T;
        d=dis[T]/2;
        while(d--) cur=road[cur];
        d=dis[T]/2;
        heart1=cur,heart2=road[cur];
        int ask1=dfs3(heart1,0,heart2);
        int ask2=dfs3(heart2,0,heart1);
        cout<<ask1*ask2%mod;
    }
    else{    中心为一个点,以"和中心点直接相连的点"分为不同的集合!!
        int cur=T;
        d=dis[T]/2;
        while(d--) cur=road[cur];
        d=dis[T]/2;
        heart1=cur;
        for(auto v:vct[heart1]) ans.emplace_back(dfs2(v,1,heart1));
        int ansss=1,sum=0;
        for(auto a:ans){
            ansss*=(a+1),ansss%=mod;
            sum+=a,sum%=mod;
        }
        if(ans.size()!=0) cout<<(ansss-sum-1+mod)%mod;
        else cout<<0;
    }
}

[ABC272E] Add and Mex - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)\

思路:

调和级数.(暴力)
易知答案的区间为[0,n],因为最多n个数字全都不一样,即ai取值为[0,n-1]是有效的
负数和大于等于n的不用考虑,那么考虑正数的话数字i作为贡献的次数有n/1,n/(i+2),n/(i+3)...n/n.
那么m次操作,每个数字被遍历到的次数的总和,就是总遍历次数,为n/1+n/2+n/3+...n/n=n(1+1/2+1/3+...+1/n)
其中(1+1/2+1/3+...+1/n)为调和级数,约为logn,,所以总复杂度约为o(nlogn)
实现写拉了。。不要一次一次枚举m然后考虑每个数字.
应该单独考虑每一个数字对每一次m的贡献.vis[i][j]为第i次操作,拥有的数字j标记为vis[i][j]=1.否则为0.
int n,m;
//vector<int> vis[200005];  两种vis都行,这个要排序
unordered_map<int,int> vis[200005];
思路:调和级数.
易知答案的区间为[0,n],因为最多n个数字全都不一样,即ai取值为[0,n-1]是有效的
负数和大于等于n的不用考虑,那么考虑正数的话数字i作为贡献的次数有n/1,n/(i+2),n/(i+3)...n/n.
那么m次操作,每个数字被遍历到的次数的总和,就是总遍历次数,为n/1+n/2+n/3+...n/n=n(1+1/2+1/3+...+1/n)
其中(1+1/2+1/3+...+1/n)为调和级数,约为logn,,所以总复杂度约为o(nlogn)
实现写拉了。。不要一次一次枚举m然后考虑每个数字.应该单独考虑每一个数字对每一次m的贡献.vis[i][j].
Add and MEX
https://www.luogu.com.cn/problem/AT_abc272_e
void solve(){      F    调和级数..
    cin>>n>>m;
    for(int i=1;i<=n;i++){
        int x; cin>>x;
        int begin=1;
        if(x<0) begin=((abs(x)-1)/i+1);   如果是负的,算出起点
        for(int j=begin;j<=m;j++){
            if(x+j*i>=n) break;
            vis[j][x+j*i]=1;
        }
    }
    for(int j=1;j<=m;j++){      看着很恐怖的m*n循环,但是实际上是完全跑不满的
        int cur=0;
        while(vis[j][cur]) cur++;
        cout<<cur<<endl;
    }
}

[ABC248C] Dice Sum - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

思路:赛时没出的简单多重背包,dp[i][j]定义为填i个数字总和为j的方案数.

const int mod=998244353;
int n,m,k;
int dp[55][2505];  dp[i][j]定义为填i个数字总和为j的方案数
Dice Sum
https://www.luogu.com.cn/problem/AT_abc248_c
void solve(){       补B--多重背包
    cin>>n>>m>>k;
    int ans=0;
    dp[0][0]=1;
    for(int i=1;i<=n;i++){
        for(int j=1;j<=k;j++){
            for(int h=1;h<=m&&h<=j;h++){
                (dp[i][j]+=dp[i-1][j-h])%=mod;
            }
            if(i==n) (ans+=dp[i][j])%=mod;
        }
    }
    cout<<ans;
}

[ABC178C] Ubiquity - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

思路:简单的容斥题,但是这方面基础太差,想错了。

全部情况为10^n,没有0的情况为9^n,没有1的情况为9^n,多减了[1,8]的情况+8^n
有一个经典错法:A22*10^(n-2),选两个位置作为09,其他位置随便填,这样算是会算重的
例如:09.....90第一个09为A22的其中一种取值,其他位置随便填.最后一个90是随便填的结果
后面又有09.....90,最后一个90为A22其中一种取值,其他位置随便填.第一个09是随便填的结果
这样就有重了.
const int mod=1e9+7;
int quickpow(int a,int b){
    int res=1;
    while(b){
        if(b&1) res*=a,res%=mod;
        a*=a,a%=mod;
        b>>=1;
    }
    return res;
}
int n;
思路:容斥,10^n-9^n-9^n+8^n
全部情况为10^n,没有0的情况为9^n,没有1的情况为9^n,多减了[1,8]的情况+8^n
有一个经典错法:A22*10^(n-2),选两个位置作为09,其他位置随便填,这样算是会算重的
例如:09.....90第一个09为A22的其中一种取值,其他位置随便填.最后一个90是随便填的结果
后面又有09.....90,最后一个90为A22其中一种取值,其他位置随便填.第一个09是随便填的结果
这样就有重了.
Ubiquity
void solve(){       补C
    cin>>n; cout<<((quickpow(10,n)-quickpow(9,n)*2%mod+mod)%mod+quickpow(8,n))%mod;
}

[ABC263D] Left Right Operation - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

思路:自己的写法是完全没问题的,但是ac得有点灵异。

正解比较合规:设x,y为左,右改变的个数,pre为原数组的前缀和数组

那么答案为L*x+R*y+pre[n-y]-pre[x].

考虑枚举x,那么L*x-pre[x]为定值,R*y+pre[n-y]考虑取最小,y的取值为[0,n-x]

那么可以o(n)预处理前缀和数组minn[j]代表y的取值为[0,j]时可以取的最小值.

int n,l,r;
int arr[200005];
int pre[200005],suf[200005];      能减少的值
int mxpre[200005],mxsuf[200005];  到达i点,最大的pre值位置为mxpre[i],最大suf值位置为mxsuf[i]
Left Right Operation
https://www.luogu.com.cn/problem/AT_abc263_d
void solve(){       E  灵异。。为什么错误的循环范围才ac??why
    cin>>n>>l>>r;
    int sum=0,maxn=0;
    for(int i=1;i<=n;i++) cin>>arr[i],sum+=arr[i];
    for(int i=1;i<=n;i++) pre[i]=pre[i-1]+(arr[i]-l);
    for(int i=n;i>=1;i--) suf[i]=suf[i+1]+(arr[i]-r);
    int maxl=1,maxr=n;
    for(int i=1;i<=n;i++){
        if(pre[i]>pre[maxl]) maxl=i;
        mxpre[i]=maxl;
    }
    for(int i=n;i>=1;i--){
        if(suf[i]>suf[maxr]) maxr=i;
        mxsuf[i]=maxr;
    }
    i=[0,n+1]就AC,不然会wa8个点... 但是mxpre[0]=0,pre[0]=0.n+1位置也都是0,那应该没有意义才对.why?
    但是问题是i=0的时候会到else语句就越界了..
    for(int i=0;i<=n+1;i++){   ----???
        if(mxpre[i]!=mxsuf[i]) maxn=max(maxn,pre[mxpre[i]]+suf[mxsuf[i]]);
        else maxn=max({maxn,pre[mxpre[i-1]]+suf[mxsuf[i]],pre[mxpre[i]]+suf[mxsuf[i+1]]});
    }
    cout<<sum-maxn;
}

  • 28
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值