2020 CCPC 威海站

A.Golden Spirit

思路:
首先可知移动老人共需要4nt时间,讨论中间等待时间。
先在2nt时间内移动老人,使位置发生交换,
此时回到初始的位置,设为左侧。

(1).
若左侧的第一个老人已经休息完毕,则可知右边的第一个也休息完毕,用2t时间移动回这两个老人并回到左侧,由于同侧的老人移动时间相差2t,此时左侧的第二个也可移动,故总时间为4nt。
(2)
若左侧的第一个老人在休息中,右边的第一个老人休息完毕,此时可以先移动到右侧移动老人或者在左侧等待第一个老人休息完毕再移动,总时间为4nt+min(t, 2t+x-2n*t)
(3)
若左右侧的第一个老人都在休息中,讨论同(2)类似。

注意:各个移动的变量之间的差值分类讨论并取最小值

#include<cstdio>
#include<iostream>
#include<algorithm>
using namespace std;

typedef long long ll;

int main(){
	ll n,x,t;
	int T; scanf("%d",&T);
	while(T--){
		scanf("%lld%lld%lld",&n,&x,&t);
		if(2*n*t>=2*t+x) printf("%lld\n",4*n*t);
		else if(2*n*t>=t+x) printf("%lld\n",min(4*n*t+2*t+x-2*n*t,4*n*t+t) );
		else printf("%lld\n",min(4*n*t+max(t, (t+x-2*n*t)), 4*n*t+(2*t+x-2*n*t)));
	}
} 

H.Message Bomb

思路:
差分+数据结构

首先朴素算法:
对于每一个学生,O(m)
遍历所有事件,O(s)
其中,每次退出一个讨论组则找到其加入时的时刻,统计区间内信息的条数,对每个group维护,O(1).

优化算法:
遍历所有事件,O(s)
若有一个学生i加入一个讨论组j,则对其接收消息数预先-sum[j],
若有一个学生i退出一个讨论组j,则对其接收消息数+sum[j]。从而实现区间内讨论组信息数的累积
若有一个学生i在讨论组j发言,则其接收消息数-1,讨论组j的消息数+1.

注意:由于最后存在学生未退出讨论组,需将未退出的讨论组退出并统计接收消息数,使用set数据结构存储 。

#include<cstdio>
#include<cmath> 
#include<set>
#include<iostream>
#include<algorithm>
using namespace std;

typedef long long ll;
const int maxn=2e5+10;

int sum[maxn];
int res[maxn];

set<int> S[maxn];

int main(){
	int n,m,s; scanf("%d%d%d",&n,&m,&s);
	int op,x,y;
	for(int i=1;i<=s;i++){
		scanf("%d%d%d",&op,&x,&y);
		if(op==1){
			res[x]-=sum[y];
			S[x].insert(y);
		}else if(op==2){
			res[x]+=sum[y];
			S[x].erase(y);
		}else{
			res[x]--;
			sum[y]++;
		}
	}
	set<int>::iterator iter;
	for(int i=1;i<=m;i++){
		
		for(iter=S[i].begin();iter!=S[i].end();iter++){
			res[i]+=sum[*iter];
		}
		printf("%d\n",res[i]);
	}
}

D.ABC Conjecture

D.题意 定义一种运算,rad(n)=n的质因子的乘积。问给出一个数c(c<=1e18)是否存在c=a+b且rad(abc)<c.

首先对c质因子分解,容易证明:
若所有因子的幂次小于等于1,则无论如何分解rad(abc)>=rad©>=c;
而对于其中幂次大于1的因子,总可以将其分解为几个素因子幂次的和,
且分解得因子的乘积小于被分解因子,将其分给a、b,其余因子不变,
可以证明rad(abc)<c。因此问题转换为求c是否有因子的幂>1,此处为本题关键

由于c范围极大(<=1e18),不能对其质因子分解。
参考网上的思路,合法的c可写作c=(y^2)*z,首先对1e6以内的素因子遍历,
判断是否存在p^2为c的因子,若是则输出“yes”,否则c除以1e6以内的因子。
剩余的c无小于1e6的因子,有两种情况,
(1)c=1此时无法继续分解,即无p^2为因子。
(2)c>1e6,此时若c包含某一因子的平方,该因子必然大于1e6,c必然大于1e12,
其除以该因子的平方后必然为1,符合则输出yes;
若不包含则为一个素因子,不存在质因子的幂次>1。

#include<cstdio>
#include<cstring>
#include<cmath> 
#include<set>
#include<iostream>
#include<algorithm>
using namespace std;

typedef long long ll;
const int maxn=1e6+10;

bool is_prime[maxn];
int prime[maxn];
int cnt=0;

int func(int N){
	memset(is_prime,true,sizeof(is_prime));
	is_prime[1]=false;
	
	for(int i=1;i<=N;i++){
		if(is_prime[i]) prime[++cnt]=i;
		for(int j=1;j<=cnt&&i*prime[j]<=N;j++){
			is_prime[i*prime[j]]=false;
			if(i%prime[j]==0) break;
		}
	}
} 

int main(){
	func(1000000);
	
	int T; scanf("%d",&T);
	ll x;
	while(T--){
		scanf("%lld",&x);
		
		bool flag=false;
		for(int i=1;i<=cnt&&prime[i]<=x&&!flag;i++){
//			printf("x=%lld, prime=%d\n",x,prime[i]);
			if(x%prime[i]==0){
				x/=prime[i];
				if(x%prime[i]==0){
					flag=true;
					break;
				}
			}
		}
		if(!flag&&x>1000000000000){
			ll y=sqrt(x);
			if(y*y==x) flag=true;
		}
		
		if(flag) printf("yes\n");
		else printf("no\n");
	}
} 

L.Clock Master

(多重背包、分组背包)
题意:
对于n个指针t1~tn代表每个指针的齿轮数及价格,
每经过一周期,各个齿轮转动一齿,
经过k个周期后的指针指向(k%t1,t%t2,…,k%tn).
问限定总共b元,如何构造多个齿轮,使不同指向的组合数最多?

由于(0,0,…,0)开始到(0,0,…,0)为一个完整周期,
则总数为lcm(t1,t2,…,tn),即求如何将b分解为多个数的和且lcm最大。

考虑分解后的两个数ti和tj,若两者有公共质因子p,
则在求lcm时仅有含p的幂最大的数对lcm有贡献,另一个数的p无贡献且占用b。
因此ti之间两两互素最优,即分解为多个质因子幂次的和。

因此按质因子分组,每一组内为质因子pi的幂次,进行多重(分组)背包dp.
由于数据范围b<=3e4,范围内的质因子及其幂次总数小,可直接朴素dp.

#include<cstdio>
#include<cstring>
#include<cmath>
#include<iostream>
#include<algorithm>
using namespace std;



const int maxn=3e4+10;

bool is_prime[maxn];
int prime[maxn];
int cnt=0; 

double ln[maxn];
double dp[maxn];

void func(int N){
	memset(is_prime,true,sizeof(is_prime));
	is_prime[1]=false;
	
	for(int i=2;i<=N;i++){
		if(is_prime[i]) prime[++cnt]=i;
		for(int j=1;j<=cnt&&i*prime[j]<=N;j++){
			is_prime[i*prime[j]]=false;
			if(i%prime[j]==0) break;
		}
	}
	printf("cnt: %d\n",cnt);
}

void init(int N){
	for(int i=1;i<=N;i++) ln[i]=log(i);
	 
	for(int i=1;i<=cnt;i++){
		for(int j=N;j>=1;j--){
			for(int k=prime[i];k<=j;k*=prime[i]){
				dp[j]=max(dp[j], dp[j-k]+ln[k]);
			}
		}
	}
}

int main(){
	func(30000);
	init(30000);
	int T,n; scanf("%d",&T);
	while(T--){
		scanf("%d",&n);
		printf("%.7lf\n",dp[n]);
	}
}

G.Gaesar Cipher(字符串哈希+线段树)

题意:给出一个数组,每个元素不超过65536,
有两种操作:
op1,x,y: 将区间[x,y]所有元素+1,大于65536则对其取模
op2,x,y,l:比较区间[x,x+l-1]和[y,y+l-1]区间内元素是否相同。

思路:
(1)首先看到比较区间内的元素是否相同,即类似字符串匹配,
由于数组长度大,考虑字符串哈希。
(2)对于数组中的元素区间+1的操作,相当于区间子串的哈希值加上sum{base^i|0<=i<(x-y+1)},可用前缀和维护。
区间操作考虑线段树。
而哈希值正好具有可加性(!),即sum[l:r]=sum[l:mid]*base^(r-mid+1)+sum[mid+1:r],
可使用线段树维护。
(3)查询一个子串的哈希值,注意两部分哈希值相加左半部分需要乘一个base的幂次。

最后考虑如何处理区间内元素溢出即大于65536:
使用一个线段树维护区间内的最大值,若最大值小于65536则不操作;否则递归查询左右区间并修改大于65536的元素,并更新线段树内维护的值。

#include<cstdio>
#include<cstring>
#include<cmath>
#include<iostream>
#include<algorithm>
using namespace std;

typedef long long ll;
const int Mod=1e9+7;
const int base=13331;
const int maxn=5e5+10;

ll p[maxn], pre[maxn];
int a[maxn];

void init(int N){
	p[1]=pre[1]=1;
	for(int i=2;i<=N;i++) p[i]=p[i-1]*base%Mod, pre[i]=pre[i-1]+p[i];
}

ll sum[maxn<<2],lazy[maxn<<2];
int tree_max[maxn<<2]; 


void pushup(int rt, int l, int mid, int r){
	sum[rt]= (sum[rt<<1]*p[r-mid+1]%Mod+sum[rt<<1|1])%Mod;
	tree_max[rt]=max(tree_max[rt<<1], tree_max[rt<<1|1]);
}
void build(int rt, int l, int r){
	if(l==r){
		sum[rt]=a[l];
		tree_max[rt]=a[l];
		return;
	}
	int mid=(l+r)/2;
	build(rt<<1,l,mid);
	build(rt<<1|1,mid+1,r);
	pushup(rt,l,mid,r);
}

void pushdown(int rt,int l, int mid, int r){
	sum[rt<<1]=(sum[rt<<1]+lazy[rt]*pre[mid-l+1]%Mod)%Mod;
	sum[rt<<1|1]=(sum[rt<<1|1]+lazy[rt]*pre[r-mid]%Mod)%Mod;
	
	tree_max[rt<<1]+=lazy[rt]; tree_max[rt<<1|1]+=lazy[rt];
	lazy[rt<<1]+=lazy[rt]; lazy[rt<<1|1]+=lazy[rt];
	
	lazy[rt]=0;
}

void update(int rt, int l, int r, int L, int R){
//	[l,r]为目标修改区间,[L,R]为当前结点对应区间 
	if(l>R||r<L) return;
	if(L>=l&&R<=r){
		sum[rt]=(sum[rt]+pre[R-L+1])%Mod;
		tree_max[rt]++;
		lazy[rt]++;
		return;
	}
	int Mid=(L+R)/2;
	if(lazy[rt]) pushdown(rt,L,Mid,R);
	update(rt<<1, l, r, L, Mid);
	update(rt<<1|1, l, r, Mid+1, R);
	pushup(rt,L,Mid,R);
}

ll query(int rt, int l, int r, int L, int R){
	if(l>R||r<L) return 0;
	if(l<=L&&R<=r){
		return sum[rt];
	} 
	int Mid=(L+R)/2; ll res=0;
	if(lazy[rt]) pushdown(rt, L, Mid, R);
	if(l<=Mid) res+=query(rt<<1,l,r,L,Mid);
	if(r>Mid) res=(res*p[min(r,R)-Mid+1]%Mod+query(rt<<1|1,l,r,Mid+1,R))%Mod;
	pushup(rt,L,Mid,R);
	return res;
	
}

void modify(int rt, int l, int r, int L, int R){
	if(tree_max[rt]<65536||l>R||r<L) return;
	if(L==R&&tree_max[rt]>=base){
		sum[rt]=tree_max[rt]=(tree_max[rt]%65536);
		return;
	}
	
	int Mid=(L+R)/2;
	pushdown(rt,L,Mid,R);
	
	modify(rt<<1,l,r,L,Mid);
	modify(rt<<1|1,l,r,Mid+1,R);
	pushup(rt,L,Mid,R);
}

int main(){
	init(500000);
	
	int n,q,op,x,y,l; scanf("%d%d",&n,&q);
	for(int i=1;i<=n;i++) scanf("%d",&a[i]);
	
	build(1,1,n);
//	printf("sum=%d\n",sum[1]);
	
	while(q--){
		scanf("%d",&op);
		if(op==1){
			scanf("%d%d",&x,&y);
			update(1,x,y,1,n); 
			
			if(tree_max[1]>=65536) modify(1,x,y,1,n);
		}else if(op==2){
			scanf("%d%d%d",&x,&y,&l);
			ll q1=query(1,x,x+l-1,1,n), q2=query(1,y,y+l-1,1,n);
//			printf("q1=%d, q2=%d\n",q1,q2);
			if(q1==q2) printf("yes\n");
			else printf("no\n");
		}
	}
} 

C.Rencontre

(注意乘积溢出问题)
思路:
首先突破点在于分析3个hotel的位置关系,
讨论不同情况得出期望距离为0.5*(dis[v1][v2]+dis[v1][v3]+dis[v2][v3])
即求上式的期望。
可拆分为求0.5*sum{E(dis[vi][vj])|1<=i,j<=3且i!=j}

因此转换为,求树上两个点集内的点、两两之间的距离,
考虑每条边对于距离dis[vi][vj]总和的贡献为:
(该边一端vi的个数×另一端vj的个数+一端vj个数×另一端vi的个数)×e.w
最终E(dis[vi][vj])=sum{dis[vi][vj]}/(cnt[vi]*cnt[vj])

使用树形dp两次dfs即可解决。

#include<cstdio>
#include<cmath>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;

typedef long long ll;
const int maxn=2e5+10;
struct Edge{
	int to,nxt,w;
};
int head[maxn];
struct Edge edge[maxn<<1];
int cnt=1;
ll dis[5][5];

int k[5];
bool hotel[maxn][5];
int fa[maxn];


int dp[maxn][5];

void add(int u, int v, int w){
	edge[cnt].to=v; edge[cnt].nxt=head[u]; edge[cnt].w=w; head[u]=cnt++;
}

void dfs1(int u){
	for(int i=1;i<=3;i++){
		if(hotel[u][i]) dp[u][i]++;
	}
	for(int i=head[u];i;i=edge[i].nxt){
		struct Edge& e=edge[i];
		if(e.to==fa[u]) continue;
		fa[e.to]=u; dfs1(e.to);
		for(int j=1;j<=3;j++) dp[u][j]+=dp[e.to][j];
	}
}

void dfs2(int u){
	for(int i=head[u];i;i=edge[i].nxt){
		struct Edge &e=edge[i];
		if(e.to==fa[u]) continue;
		for(int j=1;j<3;j++){
			for(int l=j+1;l<=3;l++){
//			i号边的一端关于第j个点集,有dp[e.to][j]个点,则另一端有k[j]-dp[e.to][j]个点 
				dis[j][l]+=1LL*(k[j]-dp[e.to][j])*dp[e.to][l]*e.w + 1LL*(k[l]-dp[e.to][l])*dp[e.to][j]*e.w;
//				dis[j][l]+=1LL*(k[j]-dp[e.to][j])*dp[e.to][l]*e.w;
			}
		}
		dfs2(e.to);
	}
}
int main(){
	double res=0;
	int n,x,u,v,w; scanf("%d",&n);
	for(int i=1;i<n;i++){
		scanf("%d%d%d",&u,&v,&w);
		add(u,v,w); add(v,u,w);
	}
	memset(hotel,false,sizeof(hotel));
	memset(fa,0,sizeof(fa));
	
	for(int i=1;i<=3;i++){
		scanf("%d",&k[i]);
		for(int j=1;j<=k[i];j++){
			scanf("%d",&x);
			hotel[x][i]=true;
		}
	}
	
	dfs1(1);
	dfs2(1);
	for(int i=1;i<3;i++){
		for(int j=i+1;j<=3;j++){
			res+=1.0*dis[i][j]/(2.0*k[i]*k[j]); // sum{0.5*dis[i][j]|1<=i,j<=3且i!=j}
		}
	}
	
	printf("%.7f\n",res);
} 
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值