2020 CCPC威海训练赛复盘

2020 CCPC威海训练赛复盘

队员:孔FC,王DH,李K

2020 China Collegiate Programming Contest, Weihai Site

ABCDEFGHIJKL
SFA (已补)SFFASFFFS

S - solved, F - failed, A - attempted

训练复盘

问题处在三道简单题A,D,H三线卡题,导致在1小时时才有提交。之后又在C题这里卡死(一个奇怪的精度问题)最后做G题的时间不太够,只做出4道题(7题为银,6题为铜)

A. Golden Spirit

题意: 2 n 2n 2n 个老人想要过桥,老人平均分布在桥的两边。每个老人都希望到桥对面再回来。老人过桥的时间均为 t t t,同时过桥后需要休息 x x x才能再过桥。老人不能单独行动,你作为一个好心人来帮老人来过桥。你过桥时间后老人相同,每次只能有一个老人过桥,问最小时间。

解:可以发现一个这样的可行解:先帮桥左边的一位老人到右边,然后帮桥右边的一位老师到左边。这样往复 2 n 2n 2n次后,原本在左边的老人全在右边,在右边的老人在左边。此时你在左边,而最开始你帮助的老人在桥右边。此时分类讨论什么时候留下先帮助左边老人,什么时候过桥帮助最开始的老人即可:

如图:
[ 1 , 2 ] − − − − − − − [ 3 , 4 ] i n i t i a l [ 2 ] − − − − − − − [ 3 , 4 , 1 ] 1 [ 2 , 3 ] − − − − − − − [ 4 , 1 ] 2 [ 3 ] − − − − − − − [ 4 , 1 , 2 ] 3 [ 3 , 4 ] − − − − − − − [ 1 , 2 ] 4 [1,2] ------- [3,4] \quad initial \\ [2] ------- [3,4,1] \quad 1\\ [2,3] ------- [4,1] \quad 2\\ [3] ------- [4,1,2] \quad 3\\ [3,4] ------- [1,2] \quad 4 \\ [1,2][3,4]initial[2][3,4,1]1[2,3][4,1]2[3][4,1,2]3[3,4][1,2]4

#include <bits/stdc++.h>
#define LL long long
using namespace std;
int main() {
    // freopen("std.in","r",stdin);
    // freopen("std.out","w",stdout);
    int T;
    scanf("%d",&T);
    while (T--) {
        LL n, x, t;
        scanf("%lld%lld%lld",&n,&x,&t);
        LL ans;
        if (2*n*t >= 2*t+x)
        ans = 4*n*t;
        else if (2*n*t >= x + t)
        ans = 4*n*t + 2*t+x-2*n*t;
        else
        ans = 4*n*t+t + max(x+t-2*n*t-t, (LL)0);
        printf("%lld\n",ans);
    }
    return 0;
    
}

C. Rencontre

题意:给定一颗边有长度的树和三个点集,问从三个点集里各任选一个点,包含三个点的结点最少子图的边权之和为 w 求w的期望.

解:

结论一:对于树上的结点 u , v u,v u,v u − v u - v uv 的路径唯一

易证明结论二:对于树上的结点 u , v , w u,v,w u,v,w,包含 u , v , w u,v,w u,v,w 三个点的结点最少子图的边权之和等于 u − v , v − w , w − u u - v,v - w, w - u uv,vw,wu 的路径长之和的一半。

至此,该题由三元问题转化为二元问题。

接下来考虑集合的问题:

可以发现标红的路径会和标蓝的路径在不同的三元路径集合中,因此可得集合1与集合2的每个路径的贡献值为集合3的元素个数。重复三次即可。(For arbitrary { u , v } ∈ S 1 , S 2 \{u,v\} \in {S_1, S_2} {u,v}S1,S2, it is contained in { u , v , w 1 } , { u , v , w 2 } , . . . , u , v , w c n t [ S 3 ] \{u,v,w_1\}, \{u,v,w_2\},...,{u,v,w_{cnt [S_3]}} {u,v,w1},{u,v,w2},...,u,v,wcnt[S3]

在这里插入图片描述

详细代码如下,求期望是最后需要除以三个集合的积。注意long longdouble在前者较大时易丢失精度的问题,应该边加边除。

#include<bits/stdc++.h>
#define LL long long
using namespace std;
const int maxn = 2e5 + 7;
struct Edge {
    int to, next, w;
};

Edge edges[maxn*2];
int frt[maxn], k;
void addEdge(int u, int v, int w) {
    edges[++k] = (Edge){v, frt[u], w};
    frt[u] = k;
}

int s[maxn];
LL edge_val[maxn];
int vis[maxn];
void dfs(int u) {
    vis[u] = 1;
    for (int i = frt[u]; i; i = edges[i].next) {
        Edge e = edges[i];
        if (vis[e.to]) continue;
        edge_val[e.to] = edge_val[u] + e.w;
        dfs(e.to);
        s[u] += s[e.to];
    }
    vis[u] = 0;
}

int cnt[4];
int a[4][maxn];
LL sum[maxn];
void search(int u, LL tol, int up) {
    vis[u] = 1;
    sum[u] = tol;
    for (int i = frt[u]; i; i = edges[i].next) {
        Edge e = edges[i];
        if (vis[e.to]) continue;
        search(e.to, tol + (up + s[u] - s[e.to] * 2) * e.w, up+s[u]-s[e.to]);
    }
    vis[u] = 0;
}

LL count(int s1, int s2, int n) {
    for (int i = 1; i <= n; i++)
    s[i] = 0;
    for (int i = 1; i <= cnt[s1]; i++)
    s[a[s1][i]] = 1;
    dfs(1);
    // for (int i = 1; i <= n; i++)
    // printf("%d ", s[i]);
    // printf("\n");
    LL tol = 0;
    for (int i = 1; i <= cnt[s1]; i++) {
        int id = a[s1][i];
        tol += edge_val[id];
    }
    search(1, tol,0);

    LL ans = 0;
    for (int i = 1; i <= cnt[s2]; i++) {
        int id = a[s2][i];
        ans += sum[id];
        //printf("%d\n",sum[id]);
    }
    return ans;
}

int main() {
    //freopen("std.in","r",stdin);
    //freopen("std.out","w",stdout);
    int n;
    scanf("%d",&n);

    for (int i = 1; i < n; i++) {
        int x, y, w;
        scanf("%d%d%d",&x,&y,&w);
        addEdge(x,y,w);
        addEdge(y,x,w);
    }

    for (int i = 1; i <= 3; i++) {
        scanf("%d", &cnt[i]);
        for (int j = 1; j <= cnt[i]; j++)
        scanf("%d", &a[i][j]);
    }

    double ans = 0;
    ans += (double) count(1,2,n) / cnt[1] / cnt[2];
    ans += (double) count(2,3,n) / cnt[2] / cnt[3];
    ans += (double) count(3,1,n) / cnt[3] / cnt[1];
    printf("%.12lf\n",ans/2);
    return 0;
}

D. ABC Conjecture

题目大意:给一个c,设 r a d ( c ) rad(c) rad(c)表示“把c的唯一分解中出现的所有质因子都抽出一个,再乘起来”,问是否存在[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 a + b = c a+b=c a+b=c使得

r a d ( a b c ) < c rad(abc)< c rad(abc)<c

先说结论:当c的唯一分解中所有质因子的次数都是1时,是no;只要有至少一个质因子的次数不是1,就是yes。

前一个no的情况很trivial,后一个其实也蛮好证明的:

设:

c = 2 1 . . . p n . . . = 2 1 . . . p × p n − 1 . . . ( n > 1 ) c=2^1...p^n...=2^1...p\times p^{n-1}...(n>1) c=21...pn...=21...p×pn1...(n>1)

那么将c分解为:

a = 2 1 . . . 1 × p n − 1 a = 2^1...1\times p^{n-1} a=21...1×pn1

b = 2 1 . . . ( p − 1 ) × p n − 1 b=2^1...(p-1)\times p^{n-1} b=21...(p1)×pn1

显然,在最坏情况下:

n = 2 n=2 n=2

r a d ( a b c ) = 2 1 . . . ( p − 1 ) × p 1 . . < c = 2 1 . . . p × p 1 rad(abc)=2^1...(p-1)\times p^1.. < c = 2^1...p\times p^1 rad(abc)=21...(p1)×p1..<c=21...p×p1

且易得这一分解方法对于任意 p p p 都适用。

也就是说,c是否yes取决于是否含有一个质因子,其次数大于1.

本来想的方向是大数质因子分解,后来队友提供了关键思路:

如果一个c是yes,那么必然有

c = a 2 × b ( a > 1 ) c = a^2\times b(a>1) c=a2×b(a>1)

(注意这里的ab含义与上述ab不同)

其中b的每个因子都不是完全平方数,也就是说,b的唯一分解中所有质因子的次数都是1;

既然c的规模是1e18,容易(?)想到:a和b中至少有一个是小于1e6的。

所以我们只需要算出1e6范围内的所有质数,然后边对c进行质因数分解边检查c的剩余部分是否是完全平方数;如果发现c的剩余部分非1且为完全平方数,说明剩下的部分是 a 2 a^2 a2;如果在质因数分解的过程中发现分解到的某个质因数的次数超过了1,则直接说明c是yes。

#define INL inline
#define REG register
#define U unsigned
#define M ((l+r)>>1)
#include <algorithm>
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <iostream>
#include <map>
#include <memory.h>
#include <queue>
#include <set>
#include <stack>
#include <string>
#include <vector>
#include <unordered_map>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
bool notprime[1145140];
int lst[114514],cntr;
void findprime(){
    for(int i=2;i<=1000005;i++){
        if(!notprime[i]){
            lst[++cntr]=i;
            for(int j=2;i*j<=1000005;j++){
                notprime[i*j]=1;
            }
        }
    }
}
INL bool isSq(ll n){
    if(n==1){
        return 0;
    }
    return n==((ll)sqrtl(n)*(ll)sqrtl(n));
}
int main(){
    //reopen("D.in","r",stdin);
    //reopen("D.out","w",stdout);
    ios::sync_with_stdio(0);
    findprime();
    int T;
    cin>>T;
    while(T--){
        ll n;
        cin>>n;
        bool ans=0;
        for(int i=1,icntr=0;i<=cntr&&n>1&&!isSq(n)&&!ans;i++,icntr=0){
            while(n%lst[i]==0){
                n/=lst[i];
                icntr++;
            }
            ans|=(icntr>1);
        }
        if(ans||isSq(n)){
            cout<<"yes"<<endl;
        }
        else{
            cout<<"no"<<endl;
        }
    }
    return 0;
}

H - Message Bomb

题目大意:有n个聊天室以及m个人,共有一下3中操作:

1. 编号为x的人加入了y聊天室

2. 编号为x的人推出了y聊天室

3. 编号为x的人在y聊天室内发了一条消息

问最终每个人收到了几条消息

首先,以每一次的3操作为切入点肯定是不行的,是一个 O ( n m ) O(nm) O(nm)的暴力

那么,考虑从12操作切入,比较容易想到,加入一个聊天室 <=> 这个聊天室之后的消息他都能收到,退出一个聊天室 <=> 这个聊天室之后的消息他都不能收到。那么,我们只需要记录当前聊天室内收到了多少消息,加入聊天室时减去这个前缀和,退出时加上这个前缀和,就能得到这个人在聊天室内收到了多少条消息。

然而,以上思路在最后将所有人手动退出他已经加入的聊天室,因此我们需要记录每个聊天室内都有哪些人,而这个操作是一个比较恶心的操作,因此,我们考虑倒着处理输入,将前缀和变为后缀和,加入聊天室时加上这个后缀和,退出时减去这个后缀和即可。

#include <bits/stdc++.h>
#define ll long long
using namespace std;
const ll maxn = 2e6+5;

ll n, m;
ll t = 0;
ll num[maxn] = {0};
ll ans[maxn] = {0};

struct node{
	ll x, y, k;
}a[maxn];

int main(){
	
	cin>>n>>m>>t;
	for(ll i = 1;i <= t;i++){
		scanf("%lld%lld%lld", &a[i].k, &a[i].x, &a[i].y);
	}
	for(ll i = t;i >= 1;i--){
		ll k = a[i].k;
		ll x = a[i].x;
		ll y = a[i].y;
		//cout<<k<<" "<<x<<" "<<y<<endl;
		if(k == 1){
			ans[x] += num[y];
		}else if(k == 2){
			ans[x] -= num[y];
		}else{
			num[y]++;
			ans[x]--;
		}
	}
	for(ll i = 1;i <= m;i++){
		printf("%lld\n", ans[i]);
	}
	return 0;
}

G. Caesar Cipher

题目大意:给定一个序列 a 1 , a 2 , . . . , a n a_1,a_2,...,a_n a1,a2,...,an,支持两种操作:

​ 操作1:指定l,r,使 a i = ( a i + 1 )   m o d   65536 ( i ∈ [ l , r ] ) a_i=(a_i+1)\ mod \ 65536 (i\in [l,r]) ai=(ai+1) mod 65536(i[l,r])

​ 操作2:指定x,y,L,判断子序列 a x , a x + 1 , . . . , a x + L − 1 a_x,a_{x+1},...,a_{x+L-1} ax,ax+1,...,ax+L1 a y , a y + 1 , . . . , a y + L − 1 a_y,a_{y+1},...,a_{y+L-1} ay,ay+1,...,ay+L1是否相等。

首先想不带修,只有操作2要怎么搞,显然一个哈希就搞定了;

接下来思考:如果带修,但操作1没有mod这个操作,也就是说只使 a i = a i + 1 a_i=a_i+1 ai=ai+1,要怎么做;

假设base值是p,那么对于序列 a l , a l + 1 , . . . , a r − 1 , a r a_l,a_{l+1},...,a_{r-1},a_r al,al+1,...,ar1,ar,它的哈希值为(设 l e n = r − l + 1 len=r-l+1 len=rl+1):

a l ⋅ p l e n − 1 + a l + 1 ⋅ p l e n − 2 + ⋯ + a r − 1 ⋅ p 1 + a r ⋅ p 0 a_l\cdot p^{len-1}+a_{l+1}\cdot p^{len-2}+\dots+a_{r-1}\cdot p^1+a_{r}\cdot p^0 alplen1+al+1plen2++ar1p1+arp0

当我们对每一位都加上1之后,哈希值就变成:

( a l + 1 ) p l e n − 1 + ( a l + 1 + 1 ) p l e n − 2 + ⋯ + ( a r − 1 + 1 ) p 1 + ( a r + 1 ) p 0 = a l ⋅ p l e n − 1 + a l + 1 ⋅ p l e n − 2 + ⋯ + a r − 1 ⋅ p 1 + a r ⋅ p 0 + ( p l e n − 1 + p l e n − 2 + ⋯ + p 1 + p ) (a_l+1) p^{len-1}+(a_{l+1}+1)p^{len-2}+\dots+(a_{r-1}+1)p^1+(a_r+1)p^0\\= a_l\cdot p^{len-1}+a_{l+1}\cdot p^{len-2}+\dots+a_{r-1}\cdot p^1+a_{r}\cdot p^0 + (p^{len-1}+p^{len-2}+\dots+p^1+p) (al+1)plen1+(al+1+1)plen2++(ar1+1)p1+(ar+1)p0=alplen1+al+1plen2++ar1p1+arp0+(plen1+plen2++p1+p)

也就是说,相当于在这个区间的哈希值上加上了 ∑ k = 0 l e n − 1 p k \sum^{len-1}_{k=0} p^k k=0len1pk

也就是base数组的前len-1项前缀和。

前缀和可以预处理出来,区间加可以在一颗线段树上维护,上推操作就是字符串合并时对哈希的操作。

现在考虑最后一个问题:加上mod 65536(即“溢出”)之后怎么办?

我们注意到这题的特点:每次区间加操作都只加1,最多5e5次操作

显然,对于每一个位置,溢出事件发生的次数是至多 ⌈ 5 × 1 0 5 65536 ⌉ = 8 \lceil \frac{5\times 10^5}{65536} \rceil =8 655365×105=8次,那么对于每一个可能出现溢出的位置,直接在线段树上暴力递归修改,返回的时候也是暴力上推即可。由于发生次数少,时间复杂度是正确的。

(思路就是这样,代码之后再贴,现在的代码一直是WA的)

L - Clock Master

题目大意:

定义对任意自然数k和任意数组 { t n } \{t_n\} {tn},(kmodt1, kmodt2, ……, kmodtn)为一个组合

给一个数字 b b b,让你将构造一个数组 { t n } \{t_n\} {tn},满足 ∑ t n < b \sum t_n < b tn<b,使得可以得到的互不相同的组合最多,输出 l n ln ln(组合个数)

实际上,组合个数就是lcm(ti), 问题转化为了使得{tn}的lcm最大。一个想法就是使得ti为p^k,其中p为质数。

那么问题就变成构造 p 1 k 1 + p 2 k 2 + p 3 k 3 + ⋯ + p n k n < b p^{k1}_1+p^{k2}_2+p^{k3}_3+\dots+p^{kn}_n < b p1k1+p2k2+p3k3++pnkn<b,使得 l n ( p 1 k 1 ⋅ p 2 k 2 ⋅ p 3 k 3 ⋅ ⋯ ⋅ p n k n ) ln(p^{k1}_1\cdot p^{k2}_2\cdot p^{k3}_3\cdot\dots\cdot p^{kn}_n ) ln(p1k1p2k2p3k3pnkn) 最大

猜想暴力枚举加记忆化搜索可能可行,但是我还没有验证。

考场上的思路是dp,暴力枚举质数pi以及幂次ki,转移方程 d p [ i ] = m a x { d p [ i ] , d p [ i − p k ] + k l n ( p ) } dp[i] = max\{dp[i], dp[i-p^k]+kln(p)\} dp[i]=max{dp[i],dp[ipk]+kln(p)},时间复杂度 O ( n 2 l o g   n ) O(\frac{n^2}{log\ n}) O(log nn2)

#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn = 1e5+5;

int pri[maxn] = {0};
int cnt = 0;
bool vis[maxn] = {0};
long double dp[maxn] = {0};
long double lg[maxn] = {0};
int ans = 0;

void pre(){
	for(int i = 2;i <= 3e4;i++){
		lg[i] = log(i);
		if(!vis[i]){
			pri[++cnt] = i;
		}
		for(int j = 1;j <= cnt &&pri[j]*i<= 3e4;j++){
			vis[pri[j]*i] = true;
			if(i%pri[j] == 0)break;
		}
	}
	
	
	for(int p = 1;p <= cnt;p++){
		for(int i = 30000;i >= 1;i--){
			int m = pri[p];
			while(m <= i){
				dp[i] = max(dp[i], dp[i-m]+lg[m]);
				m *= pri[p];
			}
		}
	}
}

int main(){
	pre();
	int t = 0;
	cin>>t;
	int x;
	while(t--){
		scanf("%d", &x);
		printf("%.7Lf\n", dp[x]);
	}
	
	return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值