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[i−1][j−h[i]−max(0,s[i]−k)][max(0,k−s[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
故总的涂色方案数即为:22+1
树的构造:自上而下构造
当前涂色方案数位
k
k
k,对于当前根结点来说,涂色方案位:1+其子树涂色方案;故子树涂色方案为
m
=
k
−
1
m=k-1
m=k−1。
对于子树的根结点,若此时
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 n−1条道路连通各城市。每一个城市中都有一个经验值为 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 n−1条道路花费建造材料的最小值。
思路:
抛去其他条件,其实就是一个最小生成树问题,但如果暴力的跑最小生成树,会T。
考虑优化:
当
n
n
n越大,出现质数的可能就越大,而质数与其他任何数的gcd都为1。
当
n
n
n大于等于1000时,结果就是
(
n
−
1
)
∗
1
(n-1)*1
(n−1)∗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;
}