Codeforces Round #774 (Div. 2)
Codeforces Round #774 (Div. 2)
contest
\(0 \implies 408\)
我是sb
problem A
有一个含 \(n+1\) 个元素的数组 \(a\),其中所有元素的和为 \(s\),有\(0 \le a_i < n\) 或 \(a_i = n^2\)。给定 \(n,s\),求数组中值为 \(n^2\)的数量。
solution:
假设有 \(x\) 个元素值为 \(n^2\),剩余元素之和为 \(y\),所以有 \(s=x \times n^2 +y\),因为有 \(n+1\) 个元素,而其中的每个元素的最大值为 \(n-1\),即 \(\max_y =(n+1) \times (n-1) = n^2-1\),可知
可知 \(x= \left \lfloor \dfrac{s}{n^2} \right \rfloor\)
时间复杂度:\(O(1)\)
code:
点击查看代码
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N=2e5;
int t,n,s;
signed main(){
cin>>t;
while(t--){
cin>>n>>s;
cout<<s/(n*n)<<endl;
}return 0;
}
problem B
solution:
给定一个长度为 \(n\) 的序列 \(a\),将其中的若干个元素染成红色或蓝色,求问是否存在染色方案,使序列中红色元素个数小于蓝色元素个数且红色元素之和大于蓝色元素之和。
思路很好想,先排序,再取前一半为 \(sum1\),另一半减去最小数后的和为 \(sum2\),比较\(sum1\) 与 \(sum2\)的大小即可。
因为这样取可以尽量使值差尽可能的大,易得这样是最佳情况。
时间复杂度:\(O(n \log_2 n + n)\),瓶颈在排序上。
code:
点击查看代码
#include <bits/stdc++.h>
#define G getchar()
#define int long long
using namespace std;const int N=3e5;int x,F;void Q(){F=x=0;}
int R(){Q();char c=G;while(c<'0'||c>'9')F|=c=='-',c=G;while(c>='0'&&c<='9')x=(x<<3)+(x<<1)+(c^48),c=G;return F?-x:x;}
int t,a[N],n,sum1,sum2;
signed main(){
t=R();while(t--){
n=R();sum1=sum2=0;for(int i=1;i<=n;i++)a[i]=R();sort(a+1,a+1+n);
if(n%2==0){for(int i=1;i<=n/2;i++)sum1+=a[i];for(int i=n/2+2;i<=n;i++)sum2+=a[i];}
else{for(int i=1;i<=n/2+1;i++)sum1+=a[i];for(int i=n/2+2;i<=n;i++)sum2+=a[i];}
if(sum2>sum1)puts("YES");else puts("NO");
}return 0;
}
problem C
给定整数 \(n\),求最少能用几个能表示为\(x!\) 或 \(2^x\)的数相加得到 \(n\)。
solution:
因为题目保证 \(n \le 10^{12}\),可知 \(n \le 14!\),所以可以用二进制枚举也就状压去做。
对于某种情况,进行状压的一般操作,即判断每一位是否有被取到。
其中要判断,如果当前数比当前状态的当前位表示的阶乘要小,就直接退出循环,因为很明显,无论后面的阶乘是否要取,都不能取了。所以直接退出。
用变量 \(cnt\) 表示当前位表示的阶乘是否被取到。
如果被取到,则更新当前情况下的答案、当前情况的数。
最后要加上剩下数值用二进制表示后1的个数。
时间复杂度:\(O(2^{14})\)
code:
点击查看代码
#include <bits/stdc++.h>
#define int long long
using namespace std;const int N=1e5,INF=1e18;
int sum[20],a,T,ans,q,p,tp,cnt,x;
signed main(){
std::ios::sync_with_stdio(false);sum[0]=1;for(int i=1;i<=15;i++)sum[i]=sum[i-1]*i;cin>>T;//预处理
while(T--){
ans=INF;cin>>a;
for(int i=1;i<=(1<<14);i++){//枚举所有可能包含或不包含每个阶乘的情况
tp=q=0;p=a;x=i;
while(x){
if(sum[++tp]>p)break;//如果数小于当前阶乘则退出
cnt=x&1;p-=(cnt==1?sum[tp]:0);x>>=1;q+=cnt;//cnt表示是否包含当前阶乘
}while(p)q+=(p&1),p>>=1;ans=min(ans,q);//加上剩下数值用二进制表示后1的个数
}cout<<ans<<endl;
}return 0;
}
problem D
给定一棵节点数为 \(n\) 的树,每个节点有点权,如果某个点的权值等于所有与之相连的节点的权值之和,则称此节点为好节点。要求构造分配方案,使在好节点个数最大的情况下总权值之和最小。
solution:
先从特殊情况 \(n=2\) 考虑。很明显在 \(n=2\) 的情况下,在两个节点的权值相同时,两个节点就都是好节点。而题目要求权值和最小,所以此时两个节点权值均设为 \(1\)。
如果 \(n>2\),可知此时一条边相连的两个节点不可能都是好节点。
下面给出证明:
- 如果两个节点的权值不同,那么权值较小的节点不可能是好节点,因为权值较大的节点与其相连。
- 如果两个节点的权值相同,那么只有在两个节点都没有其他与之相连的节点时才都是好节点,这与 \(n>2\) 矛盾。
在数据输入时用 \(val_i\) 存储节点 \(i\) 的邻居个数,如果某个节点确认可行就直接输出这个节点的邻居个数,因为很明显为了让权值和最小,好节点之外的所有节点的权值全都为 \(1\)。
很明显要用树形 DP。
定义类型为 \(pair\) 的 DP 数组 \(f\),假设 \(f_{u,i}\) 的 \(pair<x,y>\) 表示的意义为:
- \(u\) 表示当前节点编号。
- \(i\) 表示此情况下当前节点是否为好节点。
- \(x\) 表示当前情况下的子树中有几个好节点。
- \(y\) 表示当前情况下的子树中的好节点权值之和。
选取状态的标准就和题目中所描述的一样:
- 优先比较两状态下的 \(x\) 值,优先取更大的。
- 如果两状态下的 \(x\) 值相等,就取其中的 \(y\) 值更小的节点。
所以题目要求第一行输出好节点个数和所有节点的权值和,就是先按上述标准比较 \(f_{root,0} , f_{root,1}\) 然后输出 \(f_{root,0|1}^x , f_{root,0|1}^y + n - f_{root,0|1}^x\)
至于题目要求输出每个节点的权值,就再跑一次 dfs,标记那些将要作为好节点的节点。具体选取哪些也取决于上述的标准。
最后如果是好节点就输出这个节点的邻居个数,否则输出 \(1\)。
时间复杂度:\(O(n)\)
code:
点击查看代码
#include <bits/stdc++.h>
#define int long long
#define P pair<int,int>
#define M(x,y) make_pair(x,y)
#define V e[i].v
#define fi first
#define se second
using namespace std;
const int N=3e5;
int n,fir[N],c,vis[N],fa[N],flag[N],val[N];P f[N][2];
struct E{int v,nt;}e[N<<1];void I(int u,int v){e[++c].v=v;e[c].nt=fir[u];fir[u]=c;}
void dfs1(int u,int ff){
f[u][0]=M(0,0);f[u][1]=M(1,val[u]);
for(int i=fir[u];i;i=e[i].nt){
if(V==ff)continue;fa[V]=u;dfs1(V,u);
f[u][1].fi+=f[V][0].fi;
f[u][1].se+=f[V][0].se;
if(f[V][0].fi>f[V][1].fi||(f[V][0].fi==f[V][1].fi&&f[V][0].se<f[V][1].se))
f[u][0].fi+=f[V][0].fi,f[u][0].se+=f[V][0].se;
else
f[u][0].fi+=f[V][1].fi,f[u][0].se+=f[V][1].se;
}
}void dfs2(int u,int anc){
if(anc==1)flag[u]=1;
for(int i=fir[u];i;i=e[i].nt){
if(V==fa[u])continue;
if(anc==1)dfs2(V,0);
else if(f[V][0].fi>f[V][1].fi||(f[V][0].fi==f[V][1].fi&&f[V][0].se<f[V][1].se))
dfs2(V,0);
else dfs2(V,1);
}
}signed main(){
freopen("D.in","r",stdin);freopen("D.out","w",stdout);
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
cin>>n;for(int i=1,u,v;i<n;i++)cin>>u>>v,I(u,v),I(v,u),val[u]++,val[v]++;
if(n==2){puts("2 2");puts("1 1");return 0;}dfs1(1,0);
if(f[1][0].fi>f[1][1].fi||(f[1][0].fi==f[1][1].fi&&f[1][0].se<f[1][1].se)){
cout<<f[1][0].fi<<" "<<(f[1][0].se+n-f[1][0].fi)<<endl;dfs2(1,0);
}else{
cout<<f[1][1].fi<<" "<<(f[1][1].se+n-f[1][1].fi)<<endl;dfs2(1,1);
}for(int i=1;i<=n;i++)
if(flag[i])cout<<val[i]<<" ";
else cout<<"1 ";
return 0;
}
problem E
假设有一个 \(n\) 行 \(m\) 列的矩阵,其中第 \(i\) 行 \(j\) 列的元素是 \(i^j\),给出 \(n,m\),求矩阵中不同的数的个数。
solution:
首先很明显,如果要得到相同的数字,两个形如 \(x^y\),的式子的 \(x\) 必须相同。
用第 \(2,4,8\) 行的数字为例:
然后可全部转换为 \(2^x\) 的形式:
此时去掉底数,只留下指数:
剩下的就是暴力枚举了。
时间复杂度:\(O(m \log_2 n + n)\)
code:
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
int cnt[22],v[20000010],T,vis[200000],n,m,ans,c;
void init(int m){
c=0;for(int i=1;i<=20;i++){
for(int j=1;j<=m;j++)
if(!v[i*j])v[i*j]=1,c++;
cnt[i]=c;
}
}void test(){
cin>>n>>m;init(m);ans=1;for(int i=1;i<=n;i++)vis[i]=0;
for(int i=2;i<=n;i++)
if(!vis[i]){
int g=i,sum=0;
while(g<=n)vis[g]=1,sum++,g*=i;
ans+=cnt[sum];
}cout<<ans<<endl;
}signed main(){test();return 0;}
problem F
有 \(n\) 个节点首尾相连围成一个环,另有 \(n^2\) 个数值,\(1 \dots n\) 各有 \(n\) 个,每个节点有对应的唯一数值。
开始时,每个节点上有 \(n\) 个数值。
定义每次操作是同时将节点上的某个数值传给右边的节点。
定义好状态是某个节点上的 \(n\) 个数值均为这个节点对应的数值。
构造一种操作方案,使得在 \(n^2-n\) 次操作内使得所有节点都为好节点,输出构造的方案,不要求次数最少。
solution:
To be continued \(\dots\)
code:
点击查看代码
#include <bits/stdc++.h>
using namespace std;
const int N=105;
int n,m,p[N][N],x,pre[10005][N],k;
bool v[N][N],b[N],flag;
int work(int k,int i){
if(pre[k][i])return pre[k][i];
return pre[k][i]=work(k,i%n+1);
}signed main(){
// freopen("F.in","r",stdin);freopen("F.out","w",stdout);
std::ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
cin>>n;
for(int i=n;i>=1;i--)
for(int j=1;j<=n;j++){
cin>>x;
if(!v[i][x])v[i][x]=1;
else p[i][++p[i][0]]=x;
}
while(1){
flag=1;
for(int i=1;i<=n;i++)
if(p[i][0])flag=0;
else b[i]=1;
if(flag)break;k++;
for(int i=1;i<=n;i++)
if(p[i][0])pre[k][i]=p[i][p[i][0]--];
for(int i=1;i<=n;i++)work(k,i);
for(int i=1;i<=n;i++)
if(!b[i])
if(!v[i][pre[k][i%n+1]])v[i][pre[k][i%n+1]]=1;
else p[i][++p[i][0]]=pre[k][i%n+1];
}cout<<(k+n*(n-1)/2)<<endl;
for(int i=1;i<=k;i++){
for(int j=n;j>=1;j--)
cout<<pre[i][j]<<" ";
cout<<endl;
}for(int i=1;i<n;i++)
for(int j=i;j>=1;j--){
for(int k=1;k<=n;k++)
cout<<(k+j-1)%n+1<<" ";
cout<<endl;
}
return 0;
}