2021 Shandong Provincial Collegiate Programming Contest 补题

G. Grade Point Average

计算n个数字的平均数,结果保留k位小数。

思路:
k最多1e5位
手工除法模拟

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=1e5+10; 
const int mod =1e9+7;
int main(){
	int n,k;
	scanf("%d%d",&n,&k);
	int sum=0;
	for(int i=1;i<=n;i++){
		int x;
		scanf("%d",&x);
		sum+=x;
	}
	printf("%d.",sum/n);
	sum%=n;
	while(k--){
		sum*=10;
		printf("%d",sum/n);
		sum%=n;
	}
	return 0;
}  

Dyson Box

不同重力场下的推箱子游戏,求出将所有箱子推在一起之后,组合图形的周长。

思路:
每个箱子如果不与其他箱子相邻,那么贡献是4,
现考虑竖直方向的重力:
如果当前列之前不存在箱子,那么现在加入的这个箱子的贡献值为4;
如果当前列已经存在其他箱子,那么现在加入的这个箱子贡献值减2(在4的基础上);
与此同时,若前一列的箱子数量大于当前列(向下推箱子到底,当前箱子就会与前一列的某个箱子相邻),那么现在加入的这个箱子贡献值再减2;
若后一列的箱子数量也大于当前列,那么现在加入的这个箱子贡献值再减2。

水平方向的重力同理。

注意开longlong

#include <iostream>
#include <cstring>
#include <cmath>
#include <cstdio>
#include <string>
#include <algorithm>
#include <queue>
#include <utility>
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=2e5+10; 
const int mod =1e9+7;
int xx[maxn],yy[maxn];
int main(){
	int n;
	cin>>n;
	ll ans1=0;
	ll ans2=0;
	for(int i=0;i<n;i++){
		int x,y;
		cin>>x>>y;
		ans1+=4,ans2+=4;
		if(xx[x]) ans1-=2;
		if(x>=1&&xx[x-1]>xx[x]) ans1-=2;
		if(xx[x+1]>xx[x]) ans1-=2;
		if(yy[y]) ans2-=2;
		if(y>=1&&yy[y-1]>yy[y]) ans2-=2;
		if(yy[y+1]>yy[y]) ans2-=2;
		xx[x]++;
		yy[y]++;
		cout<<ans1<<" "<<ans2<<endl;
	}
	return 0;
}  

H. Adventurer’s Guild

给出怪物的数量n,人物血量H,人物的耐力S;
接下来n行,每一行为每只怪物的血量h,耐力s,价值w;
每消灭一个怪物,消耗h的血量和s的耐力
S如果为负数,需要用H去弥补S,如果H小于等于0则结束;
输出可以获得的最大价值;

思路:
二维背包
d p [ i ] [ j ] [ k ] dp[i][j][k] dp[i][j][k]:前 i i i个怪物,当前血量为 j j j,消耗 k k k耐力时能够获得的最大价值。
动态转移方程:
d p [ i ] [ j ] [ k ] = m a x ( d p [ i ] [ j ] [ k ] , d p [ i − 1 ] [ j − h [ i ] − m a x ( 0 , s [ i ] − k ) ] [ m a x ( 0 , k − s [ i ] ) ] + w [ i ] dp[i][j][k]=max(dp[i][j][k],dp[i-1][j-h[i]-max(0,s[i]-k)][max(0,k-s[i])]+w[i] dp[i][j][k]=max(dp[i][j][k],dp[i1][jh[i]max(0,s[i]k)][max(0,ks[i])]+w[i]
利用滚动数组降维

#include <iostream>
#include <cstring>
#include <cmath>
#include <cstdio>
#include <string>
#include <algorithm>
#include <queue>
#include <utility>
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=1e3+10; 
const int mod =1e9+7;
int h[maxn],s[maxn],w[maxn];
ll dp[maxn][maxn];
int main(){
	int n,H,S;
	scanf("%d%d%d",&n,&H,&S);
	for(int i=1;i<=n;i++){
		scanf("%d%d%d",&h[i],&s[i],&w[i]);
	}
	for(int i=1;i<=n;i++){
		for(int j=H;j>=1;j--){
			for(int k=S;k>=0;k--){
				if(j>h[i]&&j+k>s[i]+h[i]){
					dp[j][k]=max(dp[j][k],dp[j-h[i]-max(0,s[i]-k)][max(0,k-s[i])]+w[i]);
				}
			}
		}
	}
	printf("%lld\n",dp[H][S]);
	return 0;
}  

Matrix Problem

给出一个仅由0和1构成的矩阵,且该矩阵的外层一定为0,求满足以下条件的两个矩阵:
1、这两个矩阵的与运算结果与原矩阵相同
2、这两个矩阵中的1都是连通的

思路:
思维+构造
输入矩阵值为1的位置,两个矩阵的值也一定为1;输入矩阵值为0的位置,一个矩阵值为1,另一个矩阵的值则为0。因此我们可以先构造出一个矩阵A,根据上述规则,在一些位置取反,就可以得到另一个矩阵。
输入矩阵的外层一定为0,那么矩阵A的外层就可以直接确定
用如下图所示的方法进行构造:
1、奇数行(最后一列除外)的位置为1
2、偶数行(第一行除外)的位置为0
3、输入矩阵为1的位置一定为1
此时就算输入矩阵在偶数行出现1,也可以和其他1连通。
在这里插入图片描述

#include<bits/stdc++.h>
using namespace std;
const int maxn=505;
int a[maxn][maxn],b[maxn][maxn],c[maxn][maxn];
int main(){
	int n,m;
	cin>>n>>m;
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			char ch;
			cin>>ch;
			if(ch=='1') c[i][j]=1;
			else c[i][j]=0;
		}
	}	
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			a[i][1]=1;
			if(i&1&&j<m||(i==n&&j<m)) a[i][j]=1;
			if(c[i][j]==1) a[i][j]=1;
		}
	}
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			if(c[i][j]==1) b[i][j]=1;
			else if(a[i][j]) b[i][j]=0;
			else if(a[i][j]==0) b[i][j]=1;
		}
	}
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			cout<<a[i][j];		
		}
		cout<<endl;
	}
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			cout<<b[i][j];		
		}
		cout<<endl;
	}
	return 0;
}

Cat Virus

构造一棵树,构造的这颗树有k种不同的涂色方法(每个结点可以被涂成黑色或白色,如果某个结点被涂成了黑色,那么他的所有子树的结点也是黑色)

思路:
总的来说,每个结点都有两种涂色方案(黑色或白色),只是说当某个父结点被涂成黑色时,直接影响了其所有子树的涂色,此时只有一种涂色方案。

在这里插入图片描述
以上图为例,涂色方案计算方法为:自下而上计算
1、当根节点被涂成白色,子树结点涂色方案数:22
2、当根节点被涂成黑色:1
故总的涂色方案数即为:2
2+1

树的构造:自上而下构造
当前涂色方案数位 k k k,对于当前根结点来说,涂色方案位:1+其子树涂色方案;故子树涂色方案为 m = k − 1 m=k-1 m=k1
对于子树的根结点,若此时 m m m为奇数,则还需继续向下构造,若此时 m m m为偶数,只需增加其兄弟结点即可。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=1e5+10; 
const int mod =1e9+7;
vector<int>v[maxn];
int main(){
	ll k;
	cin>>k;
	if(k==2){
		cout<<1<<endl;
		return 0;
	}
	int root=1;
	int idx=1;
	while(--k){
		root=idx;
		while(!(k&1)){//k为偶数,添加兄弟结点 
			v[root].push_back(++idx);
			k>>=1;
		} 
		if(k!=1) v[root].push_back(++idx);
	}
	cout<<idx<<endl;
	for(int i=1;i<idx;i++){
		if(v[i].size()){
			for(int j=0;j<v[i].size();j++){
				cout<<i<<" "<<v[i][j]<<endl;
			}
		}
	}
	return 0;
}  

Build Roads

现有 n n n个城市,需要修建 n − 1 n-1 n1条道路连通各城市。每一个城市中都有一个经验值为 a i a_i ai的建造公司,修建一条连通城市 i i i和城市 j j j的道路需要花费 g c d ( a i , a j ) gcd(a_i,a_j) gcd(ai,aj)的建造材料。求修建 n − 1 n-1 n1条道路花费建造材料的最小值。

思路:
抛去其他条件,其实就是一个最小生成树问题,但如果暴力的跑最小生成树,会T。
考虑优化:
n n n越大,出现质数的可能就越大,而质数与其他任何数的gcd都为1。
n n n大于等于1000时,结果就是 ( n − 1 ) ∗ 1 (n-1)*1 (n1)1
但是如果L==R,结果就是(n-1)*L;(打个表就可以发现)
如果n小于1000,跑kruskal。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ULL;
const int maxn=2e5+10; 
const int mod =1e9+7;
int n,L,R,a[maxn];
ULL seed;
ULL xorshift64(){
    ULL x = seed;
    x ^= x << 13;
    x ^= x >> 7;
    x ^= x << 17;
    return seed = x;
}
int gen(){
    return xorshift64()%(R-L+1)+L;
}
struct edge{
    int u,v,w;
    bool operator < (const edge &a) const{
        return w>a.w;
    } 
};
priority_queue<edge> q;
int fa[maxn];
int find(int x){
    return fa[x]==x?x:fa[x]=find(fa[x]);
}
int main(){
    scanf("%d%d%d%llu",&n,&L,&R,&seed);
    for(int i=1;i<=n;i++) a[i]=gen();
    if(L==R){
        printf("%lld\n", 1ll*(n-1)*L);
        return 0;    
    }
    if(n>=1000){
        printf("%d\n", n - 1);
        return 0;
    }
    for(int i=1;i<=n;i++) fa[i]=i;
    for(int i=1;i<=n;i++){
        for(int j=i+1;j<=n;j++){
            q.push({i,j,__gcd(a[i],a[j])});
        }
    }
    int cnt=0;
    ll ans=0;
    while(!q.empty()){
        edge x = q.top(); 
		q.pop();
        int fx=find(x.u);
        int fy=find(x.v);
        if(fx==fy) continue;
        fa[fx]=fy;
        cnt++;
        ans+=x.w;
        if(cnt==n-1) break; 
    }
    printf("%lld\n", ans);
    return 0;
}

F. Birthday Cake

给出 n n n个字符串,从中任选两个拼接在一起,要求拼接成的字符串能够被拆分成两个完全一样的字符串。求有多少种选择方案。

思路:
1、对于某一个字符串,遍历其前缀和后缀,若前缀和后缀相同,则选择方案为剩余字符串出现的次数。
2、若输入中有 m m m个相同的字符串,则选择方案为 C m 2 C_m^2 Cm2

字符串哈希-双哈希

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<ll,ll> PP;
const int maxn=4e5+10; 
const ll mod1=1e9+7;
const ll mod2=1e9+9;
const int P1=13331;
const int P2=13331;
map<PP,ll>mp;
ll p1[maxn],p2[maxn],h1[maxn],h2[maxn];
string s[maxn];
void init(int n){
	p1[0]=1,p2[0]=1;
    for(int i=1;i<=n;i++){
    	p1[i]=p1[i-1]*P1%mod1;
    	p2[i]=p2[i-1]*P2%mod2;
	}
}
ll get1(int l,int r){
	return ((h1[r]-h1[l-1]*p1[r-l+1])%mod1+mod1)%mod1;
}
ll get2(int l,int r){
	return ((h2[r]-h2[l-1]*p2[r-l+1])%mod2+mod2)%mod2;
}
ll cal(ll x){
	return x*(x-1)>>1;
}
int main(){
	int n;
	cin>>n;
	int mmax=-1;
	for(int i=1;i<=n;i++){//统计每个字符串出现的次数 
		cin>>s[i];
		int len=s[i].size();
		mmax=max(mmax,len);
		s[i]=" "+s[i];
		ll tmp1=0,tmp2=0; 
		for(int j=1;j<=len;j++){
			tmp1=(tmp1*P1+(s[i][j]-'a'+1))%mod1;
			tmp2=(tmp2*P2+(s[i][j]-'a'+1))%mod2;
		}
		mp[PP(tmp1,tmp2)]++;
	}
	init(mmax);
	ll ans=0;
	for(auto i:mp) ans+=cal(i.second);
	for(int i=1;i<=n;i++){
		int len=s[i].size()-1;
		for(int j=1;j<=len;j++){//对当前字符串进行哈希
			h1[j]=(h1[j-1]*P1+(s[i][j]-'a'+1))%mod1;
			h2[j]=(h2[j-1]*P2+(s[i][j]-'a'+1))%mod2;
		}
		for(int j=1;j*2<=len;j++){
			ll has1=get1(1,j);//前缀
			ll has11=get2(1,j);//后缀
			ll has2=get1(len-j+1,len);
			ll has22=get2(len-j+1,len);
			if(has1==has2&&has11==has22){//前缀和后缀相等
				ll has3=get1(j+1,len-j);//剩余字符串
				ll has33=get2(j+1,len-j);
				ans+=mp[PP(has3,has33)];
			}
		}
	}
	printf("%lld\n",ans);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值