Codeforces Round #774 (Div. 2)
导语
日常训练,C题很可惜,因为DP不是很擅长
涉及的知识点
树形DP,背包DP,思维,数学
链接: Codeforces Round #774 (Div. 2)
题目
A Square Counting
题目大意:有n+1个整数序列 a a a,对于每个下标保证 0 ≤ a i < n 0\le a_i\lt n 0≤ai<n或者 a i = n 2 a_i=n^2 ai=n2, s s s为序列和,现在知道了 s s s和 n n n,判断最多能有几个 n 2 n^2 n2
思路:直接除就行
代码
#include <bits/stdc++.h>
#define int long long
const int inf=0x3f3f3f3f;
using namespace std;
const int maxn=1e6+5;
int t,n,s,a[maxn];
signed main() {
ios::sync_with_stdio(0);
cin.tie(0);
cin >>t;
while(t--) {
cin >>n>>s;
cout <<s/(n*n)<<endl;//获得个数
}
return 0;
}
B Quality vs Quantity
题目大意:略
思路:录入数据后直接排序,大值取1,小值取2个,累和,如果不符合条件大值个数+1,小值个数+1,直到满足条件
代码
#include <bits/stdc++.h>
#define int long long
const int inf=0x3f3f3f3f;
using namespace std;
const int maxn=2e5+5;
int t,n,s,a[maxn];
signed main() {
ios::sync_with_stdio(0);
cin.tie(0);
cin >>t;
while(t--) {
cin >>n;
for(int i=1; i<=n; i++)cin >>a[i];
sort(a+1,a+1+n);
if(n<=2) {
cout <<"NO\n";
continue;
}
int h=a[n],l=a[1]+a[2];
if(h>l) {
cout <<"YES\n";
continue;
}
bool flag=0;
for(int i=3,j=n-1; i<j;) {
h+=a[j--],l+=a[i++];
if(h>l) {
flag=1;
break;
}
}
flag?cout <<"YES\n":cout <<"NO\n";
}
return 0;
}
C Factorials and Powers of Two
题目大意:定义完美的数字 x x x, x x x要么为2的整数次幂,要么为阶乘,现在给出一个数n,找出k个不同的完美数字的和正好为n,输出k,如果存在多解,输出最小的k
思路:两种思路,一种暴搜,一种是DP
暴搜:因为存在2的次幂,所以一直有解,加入阶乘相当于让多个二进制和起来,而总体元素不多,所以可以直接暴搜尝试
DP:因为范围内满足条件的阶乘很少,只有15个,所以可以直接尝试这15个数的所有有效组合,而任意一个数可以转换为二进制,所以一个数二进制里1的个数对应需要的数字个数,那么可以尝试所有的阶乘+二进制1的组合方式取最值即可
代码(暴搜)
#include <bits/stdc++.h>
#define int long long
const int inf=1e13;
using namespace std;
const int maxn=121;
int t,n,cnt,pre[maxn],ans;
int a[maxn]= {0,1,2,4,6,8,16,24,32,64,120,128,256,512,720,1024,2048,
4096,5040,8192,16384,32768,40320,65536,131072,262144,362880,524288,
1048576,2097152,3628800,4194304,8388608,16777216,33554432,39916800,
67108864,134217728,268435456,479001600,536870912,1073741824,2147483648,
4294967296,6227020800,8589934592,17179869184,34359738368,68719476736,
87178291200,137438953472,274877906944,549755813888,1099511627776,1394852659200
};//预处理1e12内的所有特殊数字
void DFS(int res,int u,int num) {//暴搜
if(res==0) {
ans=min(ans,num);
return;
}
if(u==0)return;
if(res>pre[u])return;
//剪枝,如果前缀和都小于当前剩余的值,代表不能从前缀和中抽出值得到
if(res>=a[u])
DFS(res-a[u],u-1,num+1);
DFS(res,u-1,num);
}
signed main() {
ios::sync_with_stdio(0);
cin.tie(0);
// freopen("in.txt","w",stdout);
cin >>t;
//reverse(a+1,a+50);
for(int i=1; i<=54; i++)//前缀和
pre[i]=pre[i-1]+a[i];
while(t--) {
cin >>n;
ans=inf;
DFS(n,54,0);
ans==inf?cout <<-1<<endl:cout <<ans<<endl;
}
return 0;
}
代码(DP)
#include <bits/stdc++.h>
#define int long long
const int inf=1e13;
using namespace std;
int c[20]= {1},t,n;
vector<int>v;
map<int,int>dp;//记录阶乘组合的结果以及对应需要的元素个数
int div(int x) {
bitset<65>tmp=x;
return tmp.count();
}
signed main() {
ios::sync_with_stdio(0);
cin.tie(0);
for(int i=1; i<=15; i++)c[i]=c[i-1]*i;
v.push_back(0);
for(int i=3; i<=15; i++) {//预处理所有的阶乘的处理情况
int len=v.size();
for(int j=0; j<len; j++) {
if(dp[v[j]+c[i]]==0)dp[v[j]+c[i]]=dp[v[j]]+1;
else dp[v[j]+c[i]]=min(dp[v[j]+c[i]],dp[v[j]]+1);//取个数最少
v.push_back(v[j]+c[i]);
}
}
sort(v.begin(),v.end());
cin >>t;
while(t--) {
cin >>n;
int res=div(n),len=v.size();//初始化为二进制里1个数
for(int i=0; i<=len; i++) {
if(n-v[i]<0)break;
res=min(res,div(n-v[i])+dp[v[i]]);
//一部分用阶乘组成,另一部分用二进制
}
cout <<res<<endl;
}
return 0;
}
D Weight the Tree
题目大意:给出有n个节点的树,编号1到n,定义好节点:邻接点点权和等于该点点权,现在要为树节点赋值,求好节点数量最大的赋值方案,如果多解,输出点权和最小的方案
思路:由于权值和要最小化,那么不为好点直接设置为1即可,除了只有两个点的特殊情况外,好点和坏点必然是交替的,那么好点周围必然全是坏点,点权值也就是多个1相加,考虑dp
d
p
[
i
]
[
0
]
dp[i][0]
dp[i][0]为
i
i
i不为好节点,
d
p
[
i
]
[
1
]
dp[i][1]
dp[i][1]为好节点,
a
n
s
ans
ans为该点及其子树中好点个数,
s
u
m
sum
sum表示自己以及子树点权和,可得转移方程
- d p [ u ] [ 1 ] . a n s + = d p [ v ] [ 0 ] . a n s dp[u][1].ans+=dp[v][0].ans dp[u][1].ans+=dp[v][0].ans
- d p [ u ] [ 1 ] . s u m + = d p [ v ] [ 0 ] . s u m dp[u][1].sum+=dp[v][0].sum dp[u][1].sum+=dp[v][0].sum
- d p [ u ] [ 0 ] . a n s + = m a x . a n s dp[u][0].ans+=max.ans dp[u][0].ans+=max.ans
-
d
p
[
u
]
[
0
]
.
s
u
m
+
=
m
a
x
.
s
u
m
dp[u][0].sum+=max.sum
dp[u][0].sum+=max.sum
m a x max max为好点个数最大且点权和最小的子节点
代码
#include <bits/stdc++.h>
#define int long long
const int inf=1e13;
using namespace std;
const int maxn=2e5+5;
int t,n,cnt,head[maxn],degree[maxn],w[maxn];
struct node {
int next,to;
} e[maxn<<1];
void Add(int from,int to) {
e[++cnt].next=head[from];
e[cnt].to=to;
head[from]=cnt;
}
struct x {
int ans,sum;//子节点内(包括自己)好点个数,子树和
bool operator==(const x& a)const {
return a.ans==ans&&a.sum==sum;
}
} dp[maxn][2];
x getmax(x a,x b) {
if(a.ans>b.ans)return a;
if(a.ans==b.ans&&a.sum<b.sum)return a;
return b;
}
void DFS(int u,int f) {
dp[u][0]=(x) {//不是好点
0,1ll
};
dp[u][1]=(x) {//是好点,那么周围的所有点都不为好点,点权为度
1ll,degree[u]
};
for(int i=head[u]; ~i; i=e[i].next) {
int v=e[i].to;
if(v==f)continue;
DFS(v,u);
dp[u][1].ans+=dp[v][0].ans;
//这个点设置为好点,那么邻接点不为好点
dp[u][1].sum+=dp[v][0].sum;
//统计子树点权和
x t=getmax(dp[v][0],dp[v][1]);
//如果这个点不是好点,选择好点多且子树和少的方案
dp[u][0].ans+=t.ans;
//统计好点
dp[u][0].sum+=t.sum;
//统计子树点权和
}
}
void Print(int u,int f,bool flag) {
w[u]=flag?degree[u]:1;//如果是好点,点权为度,否则为1
for(int i=head[u]; ~i; i=e[i].next) {
int v=e[i].to;
if(v==f)continue;
Print(v,u,flag?0:getmax(dp[v][0],dp[v][1])==dp[v][1]);
//flag=1,是好点,那么邻接点v不为好点,否则判断v是不是好点
}
}
signed main() {
ios::sync_with_stdio(0);
cin.tie(0);
cin >>n;
memset(head,-1,sizeof(head));
for(int i=1,u,v; i<n; i++) {//建图
cin >>u>>v;
Add(u,v);
Add(v,u);
degree[v]++,degree[u]++;//记录度
}
if(n==2) {//特判节点数为2
cout <<"2 2\n1 1";
return 0;
}
DFS(1,1);//深搜
x t=getmax(dp[1][0],dp[1][1]);//获得最值
Print(1,1,t==dp[1][1]);//t==dp[1][1]判断1是不是好点
cout <<t.ans<<" "<<t.sum<<endl;
for(int i=1; i<=n; i++)cout <<w[i]<<" ";
return 0;
}
E Power Board
题目大意:n×m的矩阵,第i行第j列的元素为 i j i^j ij,找出矩阵中出现的所有数字的种类个数
思路:题目思路很精彩,也很复杂,我尽量讲清楚
首先可以发现一个规律,对于第
k
k
k行的第
p
p
p个数字,在
k
p
k^p
kp行的第1个数字会重复出现,那么,互为倍数且为幂次的行就可以视为一个集合,例如2,4,8,16… 和6,36,216,…,每个集合只有其内部元素进行了幂运算才能得到其余内部元素,对于每个集合,假设其最小元素为
x
x
x,那么单独从题设中
n
×
m
n×m
n×m的矩阵中可以得到如下矩阵
x
x
2
…
x
m
x
2
x
4
…
x
2
m
⋮
⋮
⋮
⋮
x
k
x
2
k
…
x
k
m
\begin{matrix} x & x^2 & \dots&x^m \\ x^2 & x^4 & \dots &x^{2m}\\ \vdots &\vdots&\vdots&\vdots\\ x^k & x^{2k} & \dots &x^{km}\\ \end{matrix}
xx2⋮xkx2x4⋮x2k……⋮…xmx2m⋮xkm
现在的任务就是从这
k
×
m
k×m
k×m的矩阵中去重,可以发现,只要幂次相同,那么矩阵对应元素就一定相同,因此可以单独处理幂数,就可以得到如下矩阵
1
2
…
m
2
4
…
2
m
⋮
⋮
⋮
⋮
k
2
k
…
k
m
\begin{matrix} 1& 2 & \dots&m \\ 2 & 4 & \dots &{2m}\\ \vdots &\vdots&\vdots&\vdots\\ k & {2k} & \dots &{km}\\ \end{matrix}
12⋮k24⋮2k……⋮…m2m⋮km
那么
k
k
k最大为多少?,求
k
k
k的最大值,也就是判断不等式
x
k
≤
n
x^k\le n
xk≤n,
x
x
x最小为
2
2
2,
n
≤
1
0
6
n\le10^6
n≤106,那么可以得到
k
k
k最大值为
20
20
20
当
x
x
x为不同的值时,
k
k
k的大小也随之变化,所得到的幂数矩阵是不一样的,但是去重的条件是一样的,就是幂数相同,那么,如果直接预处理幂数矩阵,进行幂数的去重,就可以很快针对不同的
x
x
x获得其去重之后的所有不同的幂数,因此,可以尝试
k
k
k从
1
1
1到
20
20
20的不同去重(分别对应上文提到的不同的集合,对于每个
k
k
k有数个集合是公用的),那么可以得到不同
k
k
k下不同幂数的个数
由于幂数间存在倍数关系,可以使用埃氏筛
预处理完之后,根据题目的输入,遍历每一行,对于该行首个元素,如果其幂数矩阵已经统计过,直接跳过即可,否则统计其幂数矩阵,并且标记小于
n
n
n的其所有幂数
代码
#include <bits/stdc++.h>
#define int long long
const int inf=1e13;
using namespace std;
const int maxn=1e6+5;
int n,m,sum[121],cnt;
bool vis[maxn*21];
signed main() {
ios::sync_with_stdio(0);
cin.tie(0);
cin >>n>>m;
for(int i=1; i<20; i++) {//预处理幂数矩阵,i为k值
for(int j=1; j<=m; j++)
cnt+=!vis[i*j],vis[i*j]=1;
sum[i]=cnt;//记录不同k下去重之后的数量
}
int res=1;
memset(vis,0,sizeof(vis));
for(int i=2; i<=n; i++) {
if(vis[i])continue;//代表幂数矩阵已经计算了
int s=0;
for(int j=i; j<=n; j*=i)vis[j]=1,s++;//表示已经在幂数矩阵中算过了
res+=sum[s];//统计幂数矩阵
}
cout <<res<<endl;
return 0;
}