Dashboard - Codeforces Round #678 (Div. 2) - Codeforces
A. Reorder
题意:给你一个长度为n的序列a和一个整数m,问是否等于m?
知识点:数学
思路:交换i和j的枚举顺序,就可以解决了
代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll N = 2e5+5;
const ll mod =1e9+7;
ll n,m,x;
void solve(){
cin>>n>>m;
for(int i=1;i<=n;++i){
cin>>x;
m-=x;//m减去x
}
if(m)cout<<"NO\n";//如果结果不是0,证明n个数的和不是m
else cout<<"YES\n";//结果是0,证明n个数的和是m
}
int main() {
ios::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr);
int _=1;cin>>_;
while(_--){
solve();
}
return 0;
}
B. Prime Square
题意:构造一个的矩阵,满足
(1)矩阵中每一个元素不超过
(2)矩阵中每一个元素都不是质数
(3)矩阵中每一行和每一列的元素和是一个质数
知识点:构造
思路:我们先把所有小于等于的合数取出来,
依照 红色->绿色->蓝色->紫色的顺序依次暴力判断下来,
每一列红色都是一样的数字,
绿色基于前面红色数字的和在合法范围内找一个解,也都是一样的数字,
蓝色基于每一列红色的数字的和在合法范围内找一个解
紫色基于绿色的数字和,蓝色的数字和在合法范围内找一个解
这个证明的话,大概是围绕俩个质数距离是lnn展开的,不是很会
代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll N = 1e5+5;
const ll mod =1e9+7;
ll n,p[N],cnt,now[N],len;
bool v[N];
ll mp[105][105];
void solve(){
cin>>n;
for(int i=1;i<n;++i){
for(int j=1;j<n;++j){
mp[i][j]=now[j];//红色部分
}
}
ll res=0,qres=0;
for(int i=1;i<n;++i)res+=now[i];//计算每一行红色部分的和
for(int i=1;i<=len;++i){
if(!v[res+now[i]]){//找到绿色部分一个合法的解
for(int j=1;j<n;++j)mp[j][n]=now[i];//绿色部分
qres=(n-1)*now[i];//绿色部分的和
break;
}
}
ll wres=0;
for(int i=1;i<n;++i){
for(int j=1;j<=len;++j){
if(!v[now[i]*(n-1)+now[j]]){//找到蓝色部分一个合法的解
mp[n][i]=now[j];//蓝色部分
wres+=now[j];//蓝色部分的和
break;
}
}
}
for(int i=1;i<=len;++i){
if(!v[wres+now[i]]&&!v[qres+now[i]]){//依照绿色和蓝色确定紫色部分的解
mp[n][n]=now[i];
break;
}
}//输出答案
for(int i=1;i<=n;++i){
for(int j=1;j<=n;++j){
cout<<mp[i][j]<<' ';
}
cout<<'\n';
}
}
int main() {
now[++len]=1;
for(ll i=2;i<=100000;++i){
if(!v[i])p[cnt++]=i;
for(int j=0;j<cnt&&p[j]*i<=100000;++j){
v[i*p[j]]=true;
if(i%p[j]==0)break;
}
if(v[i])now[++len]=i;//取出合数
}
ios::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr);
int _=1;cin>>_;
while(_--){
solve();
}
return 0;
}
C. Binary Search
题意:有一个二分程序,如下
现在给你三个整数n,x,pos,问长度为n的全排列中有几个排列在上面的二分程序中能在pos位置找到x?
知识点:二分,组合数学
思路:我们依照这个二分程序,如果跳l=mid+1,说明这个位置的数字比x小或者已经跳到x了,如果跳r=mid,说明这个位置的数字比x大,记big为二分程序中比x大的数字个数,small为二分程序中比x小的数字个数,
答案就是,
比x大的挑big个然后排列,比x小的挑small个然后排列,剩下n-1-big-small个全排列。
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll N = 1e5+5;
const ll mod =1e9+7;
ll jc[N],invjc[N];
ll ksm(ll a,ll b){
ll ans=1;
while(b){
if(b&1)ans=ans*a%mod;
a=a*a%mod;
b>>=1;
}
return ans;
}
ll A(ll n,ll m){//A(n,m)
if(n<m)return 0;
return jc[n]*invjc[n-m]%mod;
}
void solve(){
//预处理组合数
jc[0]=jc[1]=invjc[0]=invjc[1]=1;
for(ll i=2;i<N;++i)jc[i]=jc[i-1]*i%mod;
invjc[N-1]=ksm(jc[N-1],mod-2);
for(ll i=N-2;i>=2;--i)invjc[i]=invjc[i+1]*(i+1)%mod;
ll n,x,pos;cin>>n>>x>>pos;
ll big=0,small=0;
ll l=0,r=n,mid;
while(l<r){
mid=l+r>>1;
if(mid==pos)l=mid+1;//等于时
else if(mid<pos){//小于时
small++;
l=mid+1;
}
else {//大于时
big++;
r=mid;
}
}
ll ans=A(n-x,big)*A(x-1,small)%mod*jc[n-1-big-small]%mod;//答案
cout<<ans<<'\n';
}
int main() {
ios::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr);
int _=1;//cin>>_;
while(_--){
solve();
}
return 0;
}
D. Bandit in a City
题意:给你一个n个节点的树,1号节点为根节点,边是有向边,树上每个节点i都有个人,现在每个人都要沿着自己能走能边走到叶子节点,问怎么走能使所有叶子的人数的最大值最小?
知识点:二分,搜索
思路:最大值最小肯定满足单调性想想二分,check我们怎么写,每次对于一个x来说,相当于每个叶子的容量是x,如果叶子上有超过x个人一定是不行的,一个dfs就可以了,从叶子开始往上更新每个点还能往下走几人,不行就是超过容量限制了。
代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll N = 2e5+5;
const ll mod =1e9+7;
vector<ll>mp[N];
ll n,u,a[N],dp[N];
ll rongl=2e14;//最大的容量和
bool flag;//是否可行
void dfs(ll u,ll limit){
if(mp[u].size()==0)dp[u]=limit-a[u];//u是叶子,先把叶子上的人数减去
else {
dp[u]=0;//初始化
for(auto v:mp[u]){
dfs(v,limit);
dp[u]=min(dp[u]+dp[v],rongl);//父亲节点能往下走的
} //人数是所有孩子能往下走的人数的和
dp[u]-=a[u];//减去当前节点的人数
}
if(dp[u]<0)flag=false;//如果出现容量为负就是不可行
}
bool check(ll x){
flag=true;//默认可行
dfs(1,x);
return flag;
}
void solve(){
cin>>n;
for(ll v=2;v<=n;++v){
cin>>u;
mp[u].push_back(v);
}
ll l=0,r=0,mid,ans=0;
for(int i=1;i<=n;++i)cin>>a[i],r+=a[i];
while(l<=r){//二分
mid=l+r>>1;
if(check(mid)){
r=mid-1;
ans=mid;
}
else l=mid+1;
}
cout<<ans<<'\n';
}
int main() {
ios::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr);
int _=1;//cin>>_;
while(_--){
solve();
}
return 0;
}
E. Complicated Computations
题意:给你一个长度为n的序列a,现在要从a序列每一个子序列取一个mex组成一个新的序列b,问序列b的mex的值?
知识点:数据结构,思维
思路:一个序列的mex为x,那么代表这个序列1~x-1一定都出现过,并且x没出现,我们可以
对于每一个分成
个,比如下面这样
x=1的时候,分成了俩个红色部分
x=2的时候,分成了俩个绿色部分
x=3的时候,分成了三个棕色部分
x=4的时候,分成了俩个粉色部分
对于每一部分我们要快速知道这一段的mex的值,这样我们才能确定mex=x的序列是否存在
然后就知道了答案了。
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll N = 2e5+5;
const ll mod =1e9+7;
ll n,a[N],m;
vector<ll>ops[N],hkl[N];
struct dd{
ll l,r,id,d;
}lx[N];
ll block,ans[N],now;
ll vis[N];
inline bool cmp(dd a,dd b){
if(a.d==b.d)return a.r<b.r;
return a.d<b.d;
}
inline void add(ll x){
vis[x]++;
while(vis[now+1])now++;
}
inline void dec(ll x){
vis[x]--;
if(vis[x]==0)now=min(now,x);
while(vis[now]==0)now--;
}
void solve(){
cin>>n;
block=sqrt(n+0.5);//莫队块的大小
for(int i=1;i<=n;++i){
cin>>a[i];
ops[a[i]].push_back(i);//把每一个x的位置记录一下
}
for(int i=1;i<=n+1;++i){
ops[i].push_back(n+1);
ll l=1;
for(auto r:ops[i]){ //这部分相当于记录一下刚刚图上有分开的部分
lx[++m].l=l;
lx[m].r=r-1;
lx[m].id=m;
lx[m].d=(lx[m].l-1)/block;
if(l>r-1)m--;
l=r+1;
hkl[i].push_back(m);
}
}
sort(lx+1,lx+m+1,cmp);//往下都是莫队求区间mex了
ll l=1,r=0;
vis[0]=100;
for(int i=1;i<=m;++i){
int L=lx[i].l,R=lx[i].r;
while(l>L)l--,add(a[l]);
while(r<R)r++,add(a[r]);
while(l<L)dec(a[l]),l++;
while(r>R)dec(a[r]),r--;
ans[lx[i].id]=now+1;
}
ll kp=0;
for(int i=1;i<=n+3;++i){
bool flag=false;//默认没找到mex==i;
for(auto r:hkl[i]){
if(ans[r]==i)flag=true;//找到了mex==i
}
if(!flag){
cout<<i<<'\n';//输出答案
return ;
}
}
}
int main() {
ios::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr);
int _=1;//cin>>_;
while(_--){
solve();
}
return 0;
}
F. Sum Over Subsets
题意:有一个多重集S,现在问你满足
A是S的子集,
B是S的子集,
并且B是A的子集,
B的集合大小比A的集合大小少一,
A的集合中所有元素的公约数为一,的情况下
的结果,结果对998244353取模。
知识点:莫比乌斯反演(容斥),组合数学
思路:
设为满足A集合中所有元素公约数为x的答案。
设为满足A集合中所有元素公约数为x的倍数的答案。
根据莫比乌斯反演,反演后
我们要的答案是
现在问题转化成了求g(i)
也就是有一个多重集U,多重集中所有元素都是i的倍数,问答案
设为多重集U的元素,
为多重集的集合大小
如果,那么还要在(cnt-1)个里面挑一个删掉,剩下(cnt-2)个都是可选可不选,
这时候的贡献就是.
如果,一种情况是还要在(cnt-2)个里面挑一个删掉,剩下(cnt-3)个都是可选可不选
还有一种情况是删掉的B中的,剩下(cnt-2)个都是可选可不选
这时候的贡献就是
w[i]表示多重集S中元素i的个数
具体怎么算呢,设三个数组,
这里为什么是呢,因为我
里面对于俩个相等的数,我没有区分,会有同一个位置的数我拿俩次,这就不符合上面的讨论了,所以
,这样就不会存在一个位置的数拿两次。
运算过程中取模,cnt[i]的取模要注意,一个在数字里,一个在幂上,不能直接取模998244353
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e5+5;
const ll mod =998244353;
ll x,freq[N],limit,n;
ll cnt[N],w1[N],w2[N];
ll p[N],cntp,u[N];
bool v[N];
ll ksm(ll a,ll b){
if(b<0)return 0;
ll ans=1;
while(b){
if(b&1)ans=ans*a%mod;
a=a*a%mod;
b>>=1;
}
return ans;
}
void solve(){
cin>>n;limit=100000;
u[1]=1;//预处理莫比乌斯函数
for(ll i=2;i<=limit;++i){
if(!v[i])p[cntp++]=i,u[i]=-1;
for(int j=0;j<cntp&&p[j]*i<=limit;++j){
v[i*p[j]]=true;
if(i%p[j]==0)break;
u[i*p[j]]=-u[i];
}
}
for(int i=1;i<=n;++i){
cin>>x; //这里要分开输入
cin>>freq[x]; //不然会错
}
for(int i=1;i<=limit;++i){
for(int j=i;j<=limit;j+=i){
cnt[i]+=freq[j];//计算cnt[i]不取模
w1[i]+=1ll*freq[j]*j%mod;//计算w1[i]
if(w1[i]>=mod)w1[i]-=mod;
w2[i]+=1ll*freq[j]*j%mod*j%mod;//计算w2[i]
if(w2[i]>=mod)w2[i]-=mod;
}
}
ll ans=0;
for(int i=1;i<=limit;++i){//u[i]==0不用算,减少运算,毕竟取模比较多
if(cnt[i]<=1||!u[i])continue;//如果个数小于2说明不可能满足条件的
ll res=0;
ll p2=ksm(2ll,(cnt[i]-2)%(mod-1)),p3=ksm(2ll,(cnt[i]-3)%(mod-1));
//p2,p3里面%(mod-1)是欧拉降幂的东西
ll ct1=(cnt[i]-1)%mod,ct2=(cnt[i]-2)%mod;
//依次根据式子敲就好了
res=(res+w2[i]*ct1%mod*p2%mod)%mod;
res=(res+(w1[i]*w1[i]%mod-w2[i]+mod)%mod*(ct2*p3%mod+p2)%mod)%mod;
ans=(ans+u[i]*res%mod+mod)%mod;//刚刚推的反演
}
cout<<ans<<'\n';
}
int main() {
ios::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr);
int _=1;//cin>>_;
while(_--){
solve();
}
return 0;
}