题目
B_Build Roads
题意:给定一个N个点的无向完全图,
i
i
i和
j
j
j之前的边权是
g
c
d
(
a
i
,
a
j
)
gcd(a_i,a_j)
gcd(ai,aj),保证数组值随机,求MST(最小生成树)。
数据范围:
n
,
L
,
R
,
s
e
e
d
(
2
≤
n
≤
2
×
1
0
5
,
1
≤
L
≤
R
≤
2
×
1
0
5
,
1
≤
s
e
e
d
≤
1
0
18
)
n,L,R,seed (2 \le n \le 2 \times 10^5, 1 \le L \le R \le 2 \times 10^5, 1 \le seed \le 10^{18})
n,L,R,seed(2≤n≤2×105,1≤L≤R≤2×105,1≤seed≤1018)
分析
在具体的数据不需要去思考如何得到,把代码誊写一遍就行,反正数组中所有的数值都在L到R之间。那么根据LR的范围,可以知道,其中相邻两个质数的差值不会大于1000。而一旦有质数,那么任意数和质数gcd的结果都是1,进而得到答案就是n-1。但有 L = R L = R L=R时,gcd的值为L,即最后答案为 ( n − 1 ) × L (n-1)\times L (n−1)×L。对于其他情况,则采用Kruskral或者Prim算法计算MST即可,且不会TLE。
代码
#include<bits/stdc++.h>
using namespace std;
int n,L,R,a[200001];
unsigned long long seed;
unsigned long long xorshift64(){
unsigned long long x = seed;
x^=x<<13;
x^=x>>7;
x^=x<<17;
return seed = x;
}
int gen(){
return xorshift64() % (R-L+1)+L;
}
struct P{
int u,v,w;
}edge[2000010];//i,j,gcd(ai,aj)
int fa[1010],t = 0;
bool cmp(P a,P b){
return a.w<b.w;
}
int gcd(int a,int b){
if(b) return gcd(b,a%b);
else return a;
}
int find(int x){
if(fa[x]==x) return x;
else return fa[x] = find(fa[x]);
}
void add(int u,int v,int w){
edge[t].u = u;
edge[t].v = v;
edge[t].w = w;
++t;
}
int Kruskral(int n){
for(int i = 1;i<=n;++i) fa[i] = i;
sort(edge,edge+t,cmp);
int p,q,ans=0,cnt = 0;
for(int i = 0;i<t;++i){
p = find(edge[i].u);
q = find(edge[i].v);
if(q!=p){
ans+=edge[i].w;
fa[p] = q;
++cnt;
}
if(cnt==n-1) return ans;
}
return ans;
}
int main(){
scanf("%d%d%d%llu",&n,&L,&R,&seed);
for(int i = 1;i<=n;++i){
a[i] = gen();
}
if(L==R) cout << 1LL*(n-1)*L;
else if(n>100) cout << n-1;
else{
for(int i = 1;i<=n;++i){
for(int j = i+1;j<=n;++j){
add(i,j,gcd(a[i],a[j]));//加边入集合
}
}
cout << Kruskral(n);
}
return 0;
}
C_Cat Virus
题意:给定一棵树,黑白染色方案,满足一个黑点的子树都是黑点,白点任意。让构造出这样的一棵树,使得它的染色方案数为K。
数据范围:
K
(
2
≤
K
≤
2
⋅
1
0
18
)
K(2 \le K \le 2 \cdot 10^{18})
K(2≤K≤2⋅1018)
分析
这题需要画图,将K=2到10的图全部画出来,然后可以找到规律,K为偶数时,给它加一个子节点,那么还需要拼凑的K值减1。K为计数时,加上两个子节点,剩余K值是原本的一半(向下取整)。但是这里的规律要除去K=3这种情况。因为当K=3时,只要加一个节点就行。
综上,采取了不断简化K值得方法,不断加深树的深度,并且不断将树的子节点看做根节点向下构造。(脑子已经不够用了)并且整个过程需要模拟两遍,第一遍计算出n的大小,第二遍得出构造。
代码
#include<iostream>
using namespace std;
typedef long long ll;
int main(){
ll k,n = 1;
cin >> k;
ll s = k;
while(k>3){
if(k&1){
n+=2;//节点总数
k/=2;
}
else {
++n;
--k;
}
}
if(k==3) ++n;
cout << n<<"\n";
int r = 1,t = 1;
while(s>3){//构造树
if(s&1){
++t;
cout << r <<" "<<t<<"\n";
++t;
cout << r << " "<<t<<"\n";
r = t;//随便一个子节点做当前根节点
s/=2;
}
else{
++t;
cout <<r<<" "<<t<<"\n";
r = t;
--s;
}
}
if(s==3) cout << r<<" "<<++t<<"\n";
return 0;
}
D_Dyson Box
题意:向二维空间里放n个盒子,给出每个盒子的坐标,有水平往左和竖直往下两种重力,求重力作用之后形成的轮廓周长。
数据范围:
n
(
1
≤
n
≤
2
⋅
1
0
5
)
;
x
i
,
y
i
(
1
≤
x
i
,
y
i
≤
2
⋅
1
0
5
)
.
n (1 \le n \le 2 \cdot 10^5);x_i, y_i(1 \le x_i, y_i \le 2 \cdot 10^5).
n(1≤n≤2⋅105);xi,yi(1≤xi,yi≤2⋅105).
分析
稍加思考可以发现,不管是在哪里,在按照某一向重力移动后,只受其x或者y轴坐标上相邻两边的大小影响,当邻边有同样高度的盒子时,增加的边数减一。
代码
#include<bits/stdc++.h>
using namespace std;
int h[200001],s[200001];
int main()
{
long long ans1=0,ans2=0;
int n;
cin>>n;
int x,y;
for(int i=1;i<=n;i++)
{
scanf("%d%d",&x,&y);
if(h[x]==0)
{
ans1+=2;
}
if(h[x]>=h[x-1])
ans1++;
else
ans1--;
if(h[x]>=h[x+1])
ans1++;
else
ans1--;
h[x]++;
if(s[y]==0)
{
ans2+=2;
}
if(s[y]>=s[y-1])
ans2++;
else
ans2--;
if(s[y]>=s[y+1])
ans2++;
else
ans2--;
s[y]++;
printf("%lld %lld\n",ans1,ans2);
}
return 0;
}
F_Birthday Cake
题意:给定n个串,求有多少对串能拼出平方串(能够表示成两个相同的字符串连接在一起的,即AA)。
数据范围:
n
(
1
≤
n
≤
4
⋅
1
0
5
)
;
s
i
(
1
≤
∣
s
i
∣
≤
4
⋅
1
0
5
)
n (1 \le n \le 4 \cdot 10^5) ;s_i(1 \le |s_i| \le 4 \cdot 10^5)
n(1≤n≤4⋅105);si(1≤∣si∣≤4⋅105)
分析
题目的思路其实很明了,两个字符串相同可以连接,假如一个字符串的前缀等于后缀,和其中间剩余部分相同的字符串也可以连接。那么这里可以采用哈希的办法,计算出所有符合条件的字符串,然后匹配相加即可。但是这里的数据中字符串数目过多,单个哈希会发生自然溢出,即哈希的键值不够的情况。要尽可能减小冲突,要使用双哈希的办法来扩大范围。
对字符串哈希,还要知道其子串的哈希值,使用P进制的方法,采用了公式:
h
=
h
a
s
h
[
i
]
−
h
a
s
h
[
j
−
1
]
×
p
[
r
−
l
+
1
]
h = hash[i] - hash[j-1]\times p[r-l+1]
h=hash[i]−hash[j−1]×p[r−l+1]计算出字符串中
i
i
i到
j
j
j子串的哈希值。hash数组表示前
i
i
i个字符组成的字符串哈希值,p数组表示,有
i
i
i个长度的p进制。整体思想就是将字符串转化为一个P进制的数。
acwing841的字符串哈希看了之后这个办法就很好理解。
代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
char str[400010];
map<pair<ll,ll>,int> mp1,mp2;//mp1整体字符串,mp2内部字符串
pair<ll,ll> t;
ll h1[400010],h2[400010],p1[400010],p2[400010];
int mod1 = 1e9+7,mod2 = 998244353,pm = 107;//mod1和mod2不同的质数
pair<ll,ll> get(int l,int r){
ll a = (h1[r]-h1[l-1]*p1[r-l+1]%mod1+mod1)%mod1;//计算l到r的哈希值
ll b = (h2[r]-h2[l-1]*p2[r-l+1]%mod2+mod2)%mod2;
return make_pair(a, b);//双哈希值作为一个key扩大哈希范围
}
int main(){
ll n,ans = 0;
int len;
cin >> n;
p1[0] = p2[0] = 1;
for(int i = 1;i<=400001;++i){
p1[i] = p1[i-1]*pm%mod1;//P进制,p^0,p^1,p^2...
p2[i] = p2[i-1]*pm%mod2;
}
while(n--){
cin >> str;
len = strlen(str);
for(int i = 1;i<=len;++i){
h1[i] = (h1[i-1]*pm%mod1+str[i-1])%mod1;//hash值
h2[i] = (h2[i-1]*pm%mod2+str[i-1])%mod2;
}
t = make_pair(h1[len],h2[len]);
ans+=mp1[t];//整体
mp1[t]++;
ans+=mp2[t];//内部
for(int i = 1;i*2<len;++i){
if(get(1,i)==get(len-i+1,len)){//前后缀相同
t = get(i+1,len-i);//内部字符串
ans+=mp1[t];
mp2[t]++;
}
}
}
cout << ans;
return 0;
}
G_Grade Point Average
题意:计算n个数的平均值,精确到小数点后k位。
数据范围:
n
,
k
(
1
≤
n
,
k
≤
1
0
5
)
;
a
i
(
0
≤
a
i
≤
100
)
n,k (1 \le n, k \le 10^5);a_i(0\le a_i\le100)
n,k(1≤n,k≤105);ai(0≤ai≤100)
分析
直接逐位除法,要多少位有多少位。。。不过先计算出小数点前的数。
代码
#include<iostream>
using namespace std;
int main()
{
int n,dec,tt;
int zong=0;
cin>>n>>dec;
for(int i=0;i<n;i++){
cin>>tt;
zong+=tt;
}
int m = zong/n;
cout <<m<<".";
zong-=m*n;
for(int i = 0;i<dec;++i){
zong*=10;
cout << zong/n;
zong%=n;
}
return 0;
}
H_Adventurer’s Guild
题意:Yuna 的生命值是H,体力值是S,有n个任务,每个任务有生命值
h
i
h_i
hi和体力值
s
i
s_i
si的花费。生命值不能降到0或0以下,体力值的多余消耗会算到生命值里。求最大任务收益w。
数据范围:
n
,
H
,
S
(
1
≤
n
≤
1000
,
1
≤
H
≤
300
,
0
≤
S
≤
300
)
n,H,S (1 \le n \le 1000, 1 \le H \le 300, 0 \le S \le 300)
n,H,S(1≤n≤1000,1≤H≤300,0≤S≤300).
h
i
,
s
i
,
w
i
(
0
≤
h
i
,
s
i
≤
300
,
1
≤
w
i
≤
1
0
9
)
h_i, s_i, w_i(0 \le h_i, s_i \le 300, 1 \le w_i \le 10^9)
hi,si,wi(0≤hi,si≤300,1≤wi≤109)
分析
比较经典的背包问题了,直接对生命值和体力值二维dp。
代码
#include<iostream>
using namespace std;
typedef long long ll;
int h,s,w;
ll dp[310][310];//S:H
int main(){
int n,H,S;
cin >> n >> H >> S;
for(int i = 1;i<=n;++i){
cin >> h >> s >> w;
for(int j = S;j>=0;--j){
for(int k = H;k>0;--k){
if(j>=s&&k>h){//体力和生命大于任务所需
dp[j][k] = max(dp[j-s][k-h]+w,dp[j][k]);
}
else if(j<s&&k+j>h+s){//体力不足,总体大于所需
dp[j][k] = max(dp[0][j+k-h-s]+w,dp[j][k]);
}
}
}
}
ll ans = 0;
for(int i = 0;i<=S;++i){
for(int j = 1;j<=H;++j){
ans = max(ans,dp[i][j]);
}
}
cout << ans;
return 0;
}
J_Tuition Agent
题意:有n个人,对每个人可以花费x价值使其成为老师,也可以花费y价值使其成为学生。一个老师可以和一个学生配对,老师的R值小于学生R值,然后获得K的价值。计算最大获取价值。(结果可以为负)
数据范围:
n
,
k
(
2
≤
n
≤
100000
,
0
≤
k
≤
10000
)
n,k(2 \le n \le 100000, 0 \le k \le 10000)
n,k(2≤n≤100000,0≤k≤10000)
R
i
,
X
i
,
Y
i
(
1
≤
R
i
≤
n
,
0
≤
X
i
,
Y
i
≤
10000
)
R_i,X_i,Y_i(1 \le R_i \le n, 0 \le X_i, Y_i \le 10000)
Ri,Xi,Yi(1≤Ri≤n,0≤Xi,Yi≤10000)
分析
本题采用dp的解法,设立dp[i][j].表示前i个人里,有j个人做为老师。当每加入一个学生,则j-1。考虑到只有R值较小的才能教导R值较大的人,那么需要先对所有人按照R值排序,排前面的一定可以教导后面的人。接下来只需要对每一个人寻找最大价值即可,并且数据保证随机,那么基本不可能出现前面有500以上的教师情况。
代码
#include<bits/stdc++.h>
using namespace std;
struct node{
int r,x,y;
}a[100010];
long long dp[3][510];
bool cmp(node A,node B)
{
return A.r<B.r;
}
int main()
{
int t;
cin >> t;
long long n,k,ans=0;
while(t--)
{
scanf("%lld%lld",&n,&k);
ans = -0x3f3f3f3f;
for(int i=1;i<=n;i++)
{
scanf("%d%d%d",&a[i].r,&a[i].x,&a[i].y);
}
sort(a+1,a+n+1,cmp);
for(int i = 0;i<=500;++i) dp[0][i] = -0x3f3f3f3f;
dp[0][0] = 0;
//n值过大,对dp优化,dp[0]为上一次状态,dp[1]为当前状态
for(int i = 1;i<=n;++i){
for(int j = 0;j<=500;++j) dp[1][j] = -0x3f3f3f3f;
for(int j = 0;j<=500;++j){
//tuition +1
if(j<500) dp[1][j+1] = max(dp[1][j+1],dp[0][j]-a[i].x);
//student +1
if(j) dp[1][j-1] = max(dp[1][j-1],dp[0][j]-a[i].y+k);
else dp[1][j] = max(dp[1][j],dp[0][j]-a[i].y);
}
for(int j = 0;j<=500;++j) dp[0][j] = dp[1][j];
}
for(int i = 0;i<=500;++i) ans = max(ans,dp[0][i]);
cout << ans<<"\n";
}
return 0;
}
M_Matrix Problem
题意:给定0/1矩阵C,构造两个矩阵A,B,其中1 形成了完整的不分散的一块四连通块,并且对于C中所有位置,若是1,则A,B对应位置必须都是1 ,否则A,B之中必须有一个这个位置为0。
保证C阵的边框都是0。
数据范围:
n
,
m
(
3
≤
n
,
m
≤
500
)
n,m (3 \le n, m \le 500)
n,m(3≤n,m≤500)
分析
构造矩阵AB,因为是上下左右四个方向有1即为相连,因此AB可以分奇偶行涂色,这样不论C中的1在哪里,一定和AB是连通的,同时,C的外围一定是0,那么只需要将左右两侧的列,AB各涂掉一列所有即可。
代码
#include<iostream>
using namespace std;
int a[510][510],b[510][510],c[510][510];
int main(){
int n,m;
char ch;
cin >> n >> m;
for(int i = 1;i<=n;++i){
for(int j = 1;j<=m;++j){
cin >> ch;
if(ch=='1') a[i][j] = b[i][j] = c[i][j] = 1;
}
}
for(int i = 1;i<=n;i+=2){
for(int j = 1;j<=m;++j){
a[i][j] = 1;
}
}
for(int i = 2;i<=n;i+=2){
for(int j = 1;j<=m;++j){
b[i][j] = 1;
}
}
for(int i = 1;i<=n;++i){
a[i][1] = b[i][m] = 1;
a[i][m] = b[i][1] = 0;
}
for(int i = 1;i<=n;++i){
for(int j = 1;j<=m;++j){
cout <<a[i][j];
}
cout <<"\n";
}
for(int i = 1;i<=n;++i){
for(int j = 1;j<=m;++j){
cout <<b[i][j];
}
cout <<"\n";
}
return 0;
}