【Codeforces】【Div2】1070(cf 2176)
这一场感觉在梦游啊,三题青表现分直接掉100多分。半年以来第一次三题和青表现分吧,得出教训:脑子不清醒的时候一定不要打CF!
A Operations with Inversions
贪心,略。
B Optimal Shifts
贪心,略。
C Odd Process
题意:你有 \(n\) 个数,每个数有一个值 \(a_i\) 。你现在需要进行 \(k\) 轮操作,每次操作可以把一个数装进袋子里(一个数只能装一次)。当袋子里的数的 \(a_i\) 的总和为偶数时,袋子清空。现在请你给出当 \(k\) 分别为 1,2,3,...,n 时,你可以得到袋子里的数的总和最大是多少(如果最后一次操作后,袋子里的总和为偶数,袋子仍会清空)。
题解:说实话看了就不想写。分讨即可。如果袋子没有数,则你只能放奇数,否则放入一个偶数就清空一个。如果袋子有数,则此时总和一定为奇数,你只能放偶数,否则袋子会被清空。所以袋子里如果有数,则必然是一个奇数加若干个偶数。所以如果 \(k\le\) 1+偶数的个数,答案为最大的那个奇数加剩下最大的若干个偶数(注意特判没有奇数的情况)。当 \(k>\) 1+偶数的个数时,次数我们不得不放多余 1 个奇数,也就是袋子一定会被清空至少 1 次。假设把最大的奇数和所有偶数全放进去后还需要放入 \(c\) 个数。当 \(c\) 为偶数时,我们可以先放入 \(c\) 个奇数,此时袋子会被清空,然后就可以放入最大的奇数和所有偶数,答案即为它们之和;当 \(c\) 为奇数时,此时如果我们可以将 \(c\) 变成偶数即多拿一个奇数少拿一个偶数,则情况就又变成 \(c\) 为偶数的情况,答案也如前面一样求即可(注意此时有一个偶数放不了袋子中)。如果 \(c\) 不能变成偶数,也就是奇数已经用完的情况,此时也就是 \(k=n\) 的情况且 \(c\) 为奇数即我们有偶数个奇数。所以此时不管怎么放,袋子一定会被清空,答案为0。
代码:(很丑陋的代码)
#include<bits/stdc++.h>
using namespace std;
#define LL __int128
#define ll long long
#define PLL pair<ll,ll>
const ll N=2e5+10,mod=998244353,INF=2e18;
ll T,n,m;
void solve(){
cin>>n;
vector<ll> a(n+1),res(n+1);
for(int i=1;i<=n;i++) cin>>a[i];
ll odd=0,even=0;
vector<ll> O(1,INF),E(1,INF);
for(int i=1;i<=n;i++){
if(a[i]%2==0) even++,E.push_back(a[i]);
else odd++,O.push_back(a[i]);
}
sort(E.begin(),E.end(),greater<ll>());
sort(O.begin(),O.end(),greater<ll>());
ll p=0,q=0;
vector<ll> sO(n+1),sE(n+1);
for(int i=1;i<=odd;i++) sO[i]=sO[i-1]+O[i];
for(int i=1;i<=even;i++) sE[i]=sE[i-1]+E[i];
for(int i=1;i<=n;i++){
if(odd==0){
cout<<0<<" ";
continue;
}
if(even==0){
if(i%2==0){
cout<<0<<" ";
}
else cout<<O[1]<<" ";
continue;
}
if(i<=even+1){
cout<<O[1]+sE[i-1]<<" ";
continue;
}
ll t1=even+1;
ll c=i-t1;
if(c%2==0){
cout<<O[1]+sE[even]<<" ";
}
else{
if(c+1==odd){
cout<<0<<" ";
continue;
}
else cout<<O[1]+sE[even-1]<<" ";
}
}
cout<<endl;
}
int main(){
ios::sync_with_stdio(0),cin.tie(0);
T=1;
cin>>T;
while(T--){
solve();
}
return 0;
}
D Fibonacci Paths
题意:你有一个 \(n\) 个点和 \(m\) 条边的有向图,每个点有点权 \(a_i\)。现在你需要计算这个图上所有可以形成广义斐波那契数列的路径数量,模数为998244353。广义斐波那契数列:如果一个序列 \(a\) 有大于等于两个值,且当 \(i\ge 2\) 时,有 \(a_i = a_{i-1} + a_{i-2}\) 则称这个序列是广义斐波那契数列。如果一个路径是广义斐波那契数列,则有将这条路径的点权组成的数列是广义斐波那契数列。
数据范围:\(2 \le n \le 2 \cdot 10^5\),\(1 \le m \le 2 \cdot 10^5\),\(1 \le a_i \le 10^{18}\)。
题解:对于一个广义斐波那契数列,我们有一个显然的观察:当 \(i \ge 2\) 时,有 \(a_i > a_{i-1}\) 。所以我们将所有点排序,一定是小的点给大的点贡献。注意 \(a_0\) 和 \(a_1\) 没有这样的关系,也就是我们首先长度为 2 的广义斐波那契数列的贡献求出来(长度为 2 其实对应一条边,也就是每条边求一下即可)。这里我用 unmap 实现,具体来说,对每个点建一个 unmap,\(mp [u] [v]\) 表示当达点 \(u\) 且下一个数为 \(v\) 的数量。然后按照点权从小到大计算即可。(赛时很快就想到了,然后不知道哪根筋搭错了实现假了并且给正解否了)
代码:
#include<bits/stdc++.h>
using namespace std;
#define LL __int128
#define ll long long
#define PLL pair<ll,ll>
const ll N=2e5+10,mod=998244353,INF=2e18;
ll T,n,m;
ll h[N],e[N*2],ne[N*2],idx;
ll w[N];
void add(int a,int b){
e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}
void solve(){
cin>>n>>m;
vector<PLL> a(n+1);
unordered_map<ll,ll> hs[n+1];
for(int i=1;i<=n;i++){
cin>>a[i].first;
w[i]=a[i].first;
a[i].second=i;
}
for(int i=1;i<=m;i++){
ll A,b;
cin>>A>>b;
add(A,b);
}
for(int i=1;i<=n;i++){
ll vi=w[i];
for(int j=h[i];~j;j=ne[j]){
ll jj=e[j];
ll v=w[jj];
hs[jj][vi+v]++;
}
}
sort(a.begin()+1,a.end());
ll res=0;
for(int x=1;x<=n;x++){
ll u=a[x].second;
ll v=a[x].first;
for(int i=h[u];~i;i=ne[i]){
int j=e[i];
ll wj=w[j];
ll t=hs[u][wj];
if(wj==v) continue;
hs[j][v+wj]=(hs[j][v+wj]+t+mod)%mod;
}
}
for(int i=1;i<=n;i++){
for(auto t:hs[i]){
res=(res+t.second)%mod;
}
}
cout<<res<<endl;
idx=0;
for(int i=1;i<=n;i++) h[i]=-1;
}
int main(){
ios::sync_with_stdio(0),cin.tie(0);
T=1;
cin>>T;
memset(h,-1,sizeof h);
while(T--){
solve();
}
return 0;
}
E Remove at the lowest cost
题意:你有一个含 \(n\) 个元素的序列,每个元素都有两个值 \(a_i\) 和 \(c_i\) 。你需要进行 \(n-1\) 次操作。一次操作是:在剩下的序列中,选取两个相邻的元素,你可以移除 \(a_i\) 较小的那个元素(若一致则随便移除一个),并花费两个元素中较小的 \(c_i\) 的代价。你需要求 \(n-1\) 次操作后,花费的最小代价。同时有一个排列 \(p\) ,从前往后的读入排列的数并将 \(c_{p_i}\) 的置为 0(这个操作是持久的),每次置零后,对当前序列给出进行 \(n-1\) 次操作的最小代价。(注意没有置零的最小代价也要给出)
数据范围:\(2 \le n \le 2 \cdot 10^5\),\(1 \le a_i,c_i \le 10^9\)。
题解:最小花费策略:每次选最小的 \(c_i\) 进行操作直到需要把自己移除,如此反复(每次都是选最小花费且尽可能地将最多的数移除,这个策略显然是最优的)。所以对于每个数我们有一个其最优策略花费代价,设其为 \(d_i\) 。最后答案就是 \(\sum d_i - max(d_i)\) 。区间和区间最值,这启示我们用线段树解决问题。现在考虑置零的情况,将一个 \(c_i\) 置零,结合之前的最优策略,无非就是将一个数提前操作而已。假设我们选取的数为 \(x\) (这个 \(x\) 表示的是位置而不是元素的值),那么我们可以用该数将其左边和右边 \(a_i \le a_x\) 的数删去,也就是说每个数可以用 \(c_i * len\) 的代价删去一个区间的数。如果将一个 \(c_i\) 置零,也就是将该数对应的区间的 \(d_i\) 全部置零,也就是区间置零。现在我们还需要求出每个数对应的区间和 \(d_i\) 。设一个数可以消除的区间为 \([l,r]\) ,这个数的位置为 \(id\) ,则我们有 \(max[l,id] = a_{id}\) 且 \(max[i,id] > a_{id} (i < l)\) 。所以我们可以二分 + st 表求出 \(l\) ,对于 \(r\) 也是如此。\(d_i\) 的求法:先用 set 存 1-n 的位置,然后以 \(c_i\) 为基准从小到大的遍历位置,对于每个 \(i\) 的 \([l,r]\) 我们可以 \(lower \_ bound\) 其 \(l\) ,然后使用迭代器加到 \(r\) ,将这些位置都赋值当前的 \(c_i\) ,最后从 set 中删去这些位置即可。总共的时间复杂度为 \(O(n \log n)\) 。(感觉 E < D ,至少没什么思维难度)
代码:
#include<bits/stdc++.h>
using namespace std;
#define LL __int128
#define ll long long
#define PLL pair<ll,ll>
const ll N=2e5+10,mod=998244353,INF=2e18;
ll T,n,m;
ll pre[N],aft[N],mn[N];
ll a[N],w[N],p[N];
ll f[N][20],g[N][20],Log[N];
struct seg_node
{
ll l,r,sum,lz,mx;
};
struct seg_tree
{
vector<seg_node> tr;
seg_tree(int n){
for(int i=0;i<=4*(n+1);i++){
seg_node t={0,0,0,0,0};
tr.push_back(t);
}
}
void pushup(int u){
tr[u].sum=tr[u<<1].sum+tr[u<<1|1].sum;
tr[u].mx=max(tr[u<<1].mx,tr[u<<1|1].mx);
}
void pushdown(int u){
if(tr[u].lz){
tr[u<<1].lz=tr[u<<1|1].lz=1;
tr[u<<1].sum=0;
tr[u<<1|1].sum=0;
tr[u<<1].mx=tr[u<<1|1].mx=0;
tr[u].lz=0;
}
}
void build(int u,int l,int r){
if(l==r) tr[u]={l,r,mn[l],0,mn[l]};
else{
tr[u]={l,r};
int mid=l+r>>1;
build(u<<1,l,mid);
build(u<<1|1,mid+1,r);
pushup(u);
}
}
void modify(int u,int l,int r){
if(l>r) return ;
if(l<=tr[u].l&&tr[u].r<=r){
tr[u].lz=1;
tr[u].mx=0;
tr[u].sum=0;
return;
}
pushdown(u);
int mid=tr[u].l+tr[u].r>>1;
if(l<=mid) modify(u<<1,l,r);
if(r>mid) modify(u<<1|1,l,r);
pushup(u);
}
ll query(){
return tr[1].sum-tr[1].mx;
}
};
ll qma(ll l,ll r){
int k=Log[r-l+1];
return max(f[l][k],f[r-(1<<k)+1][k]);
}
ll qmi(int l,int r){
int k=Log[r-l+1];
return min(g[l][k],g[r-(1<<k)+1][k]);
}
ll get_aft(ll id){
ll l=id,r=n;
while(l<r){
ll mid=l+r+1>>1;
if(qma(id,mid)<=a[id]) l=mid;
else r=mid-1;
}
return l;
}
ll get_pre(ll id){
ll l=1,r=id;
while(l<r){
ll mid=l+r>>1;
if(qma(mid,id)<=a[id]) r=mid;
else l=mid+1;
}
return l;
}
void init(){
for(int i=1;i<=n;i++) f[i][0]=g[i][0]=a[i];
//预处理的区间最大值
for(int j=1;j<=20;j++){
for(int i=1;i+(1<<j)-1<=n;i++){
f[i][j]=max(f[i][j-1],f[i+(1<<(j-1))][j-1]);
}
}
//预处理区间最小值
for(int j=1;j<=20;j++){
for(int i=1;i+(1<<j)-1<=n;i++){
g[i][j]=min(g[i][j-1],g[i+(1<<(j-1))][j-1]);
}
}
set<ll> s;
s.insert(0),s.insert(INF);
vector<PLL> fz;
for(int i=1;i<=n;i++){
s.insert(i);
fz.push_back({w[i],i});
pre[i]=get_pre(i);
aft[i]=get_aft(i);
}
sort(fz.begin(),fz.end());
for(int i=0;i<n;i++){
ll id=fz[i].second;
ll pr=pre[id];
ll af=aft[id];
auto it=s.lower_bound(pr);
vector<ll> er;
while(1){
if((*it)>af){
break;
}
mn[(*it)]=w[id];
er.push_back((*it));
it++;
}
for(auto t:er) s.erase(t);
}
}
void clear(){
for(int i=0;i<=n;i++){
for(int j=0;j<20;j++){
f[i][j]=g[i][j]=0;
}
}
}
void solve(){
cin>>n;
for(int i=1;i<=n;i++) cin>>a[i];
for(int i=1;i<=n;i++) cin>>w[i];
for(int i=1;i<=n;i++) cin>>p[i];
init();
seg_tree tr(n+1);
tr.build(1,1,n);
cout<<tr.query()<<" ";
for(int i=1;i<=n;i++){
ll pr=pre[p[i]],af=aft[p[i]];
if(i==9){
ll ss=1;
}
tr.modify(1,pr,af);
cout<<tr.query()<<" ";
}
cout<<endl;
clear();
}
int main(){
ios::sync_with_stdio(0),cin.tie(0);
T=1;
cin>>T;
Log[1]=0;
for(int i=2;i<=N-5;i++){
Log[i]=Log[i/2]+1;
}
while(T--){
solve();
}
return 0;
}
F Omega Numbers
题意:我们设 \(\omega(n)\) 的值为 \(n\) 的素因数拆分的素数个数,例如:\(\omega(12) = \omega(2*2*3) =2\) 。有一个长度为 \(n\) 的序列 \(a\) 和一个自然数 \(k\) ,定义 \(f(a,k) = \sum_{i<j} \omega(a_i \cdot a_j)^k\) 。现在请你计算 \(f(a,k)\) ,模数为 998244353。
数据范围:\(1 \le n \le 2 \cdot 10^5\) ,\(1 \le k \le 10^9\) ,\(1 \le a_i \le n\) 。
题解:我们知道如果想让 \(\omega(n)\) 尽可能大,则 \(n\) 需要为不同的质数相乘,因为 \(n \le 2 \cdot 10^5\) ,所以 \(\omega(n) \le 6\) ,所以 \(\omega(a_i \cdot a_j) \le 12\) 。这启示我们 \(\omega(n)\) 的值很小,我们可以计算等于 1 到 12 的 \(\omega(n)\) 的数量,然后计算出答案。根据定义,有等式 \(\omega(a_i \cdot a_j) = \omega(a_i) + \omega(a_j) - \omega(\gcd(a_i,a_j))\) 成立。那么我们就可以设置 dp 状态:\(dp[n][k]\) 代表 \(\omega(a_i) + \omega(a_j) = k\) 且 \(\gcd(a_i,a_j) = n\) 的数量。考虑如何求这个值。我们另设一个状态 \(cnt[i][j]\) 代表序列 \(a\) 中为 \(i\) 的倍数且 \(\omega\) 的值为 \(j\) 的元素的数量。这个利用调和级数的形式求解即可,复杂度为 \(O(n \log n)\) 。然后从大到小的遍历 \(dp[n][k]\) 中的 \(n\) ,我们可以得到 \(dp[n][i+j] = cnt[n][i] \cdot cnt[n][j]\) (\(i,j\) 是从 1 到 6 的遍历,当 \(i = j\) 时,式子变为 \(dp[n][i+j] = cnt[n][i] \cdot (cnt[n][j] - 1 )\))。因为 \(cnt[i][j]\) 的第一维的含义是 \(i\) 的倍数,所以此时我们求得的 \(dp[n][k]\) 中含有 \(n\) 的倍数的贡献,所以我们要减去 \(dp[2n][k],dp[3n][k],...\) 。时间复杂度为 \(O(K^2n + Kn \log n)\) (\(K=6\))。最后根据得到的 \(dp[n][k]\) 求值即可,注意最后答案要除 2,最后时间复杂度的瓶颈在于素因数拆分的 \(O(n \sqrt n)\) 。(感觉 F > E,为什么 E 过得人更少呢)
代码:
#include<bits/stdc++.h>
using namespace std;
#define LL __int128
#define ll long long
#define PLL pair<ll,ll>
const ll N=2e5+10,mod=998244353,M=505;
ll T,n,m,k;
ll g[N];
ll gcd(ll a, ll b){
return b ? gcd(b, a % b) : a;
}
ll qmi(ll a,ll b,ll p){
ll res=1;
a=a%p;
while(b){
if(b&1) res=res*a%p;
a=a*a%p;
b=b>>1;
}
return res;
}
array<ll,3> get(ll x,ll sn){
vector<ll> a;
ll y1=1,y2;
ll res=0;
// ll xx=x;
for(ll i=2;i*i<=x;i++){
if(x==1) break;
if(x%i==0){
y1=y1*i;
a.push_back(i);
res++;
while(x%i==0) x=x/i;
}
}
if(x!=1){
if(x<=sn){
y1=y1*x;
res++;
y2=1;
}
else{
y2=x;
res++;
}
}
else y2=1;
return {y1,y2,res};
}
void init(){
ll n=N-5;
for(int i=1;i<=n;i++){
g[i]=get(i,n)[2];
}
}
void solve(){
ifstream fin;
// fin.open("cs/t1.in");
cin>>n>>k;
// fin>>n>>k;
vector<ll> a(n+1),s(n+1);
vector<vector<ll>> cnt(n+1,vector<ll>(15));
vector<vector<ll>> dp(n+1,vector<ll>(15));
for(int i=1;i<=n;i++) cin>>a[i],s[a[i]]++;
for(int i=1;i<=n;i++){
for(int j=i;j<=n;j+=i){
cnt[i][g[j]]+=s[j];
}
}
for(int i=n;i>=1;i--){
for(int x1=0;x1<=6;x1++){
for(int x2=0;x2<=6;x2++){
if(x1!=x2) dp[i][x1+x2]=(dp[i][x1+x2]+cnt[i][x1]*cnt[i][x2])%mod;
else dp[i][x1+x2]=(dp[i][x1+x2]+cnt[i][x1]*(cnt[i][x2]-1))%mod;
}
}
for(int x=i*2;x<=n;x+=i){
for(int j=1;j<15;j++){
dp[i][j]=(dp[i][j]-dp[x][j]+mod)%mod;
}
}
}
ll res=0;
for(int i=1;i<=n;i++){
for(int j=0;j<15;j++){
res=(res+dp[i][j]*qmi(j-g[i],k,mod))%mod;
}
}
cout<<res*qmi(2,mod-2,mod)%mod<<endl;
}
int main(){
ios::sync_with_stdio(0),cin.tie(0);
T=1;
cin>>T;
init();
while(T--){
solve();
}
return 0;
}
如有错误,敬请指出,不胜感激!

浙公网安备 33010602011771号