本文主要是介绍24.9.1(康托展开),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
上星期三:
补 24牛客多校 二 C 牛客传送门
思路: 赛时写模拟写的很臭,如果用dp写就很方便
代码如下:
const int N=2e6+10;
const int mod=1e9+7;
ll n;
char s[N][2];
int dp[N][2];
void solve(){cin >> n;for(int i=1;i<=n;i++) cin >> s[i][0];for(int i=1;i<=n;i++) cin >> s[i][1];if(s[1][0]=='R') dp[1][0]=(s[1][0]=='R')+(s[1][1]=='R');if(s[1][1]=='R') dp[1][1]=(s[1][1]=='R')+(s[1][0]=='R');int ans=max(dp[1][0],dp[1][1]);for(int i=2;i<=n;i++){dp[i][0]=(s[i][0]=='R');dp[i][1]=(s[i][1]=='R');ans=max(dp[i][0]+dp[i][1],ans);if(s[i][0]=='R' && s[i-1][0]=='R')dp[i][0]=max(dp[i-1][0]+1,dp[i][0]),ans=max(dp[i][0],ans);if(s[i][1]=='R' && s[i-1][1]=='R')dp[i][1]=max(dp[i-1][1]+1,dp[i][1]),ans=max(dp[i][1],ans);int dp0=dp[i][0],dp1=dp[i][1];
// if(s[i][1]=='R' && s[i-1][0]=='R' && s[i][0]=='R')
// dp[i][1]=max(dp0+1,dp[i][1]),ans=max(dp[i][1],ans);
// if(s[i][0]=='R' && s[i-1][1]=='R' && s[i][1]=='R')
// dp[i][0]=max(dp1+1,dp[i][0]),ans=max(dp[i][0],ans); //不该判上一列if(s[i][0]=='R' && s[i][1]=='R'){dp[i][0]=max(dp1+1,dp[i][0]);dp[i][1]=max(dp0+1,dp[i][1]);ans=max({dp[i][0],dp[i][1],ans});}}if(ans) cout << ans-1 << "\n";else cout << 0;
}
因为上星期的题量太少,没咋写周记,就给并掉了
星期二:
补 24牛客多校二 B 牛客传送门
题意:给一图和q次询问,每次询问给一图内点集,问子图的最小生成树
题解pdf
思路:最小生成树用kk算法处理,如何取边采用根号分治,若 k <= ,双重循环枚举点集,取出存在的边,若 k > ,枚举 m取出有效边
然后跑克鲁斯卡尔。因为 k的sum是有限制的,所以采用对应的枚举方法可有效降低总体复杂度
代码如下:
const int N=2e5+10,M=210;
const int INF=0x3f3f3f3f;
const int mod=998244353;
ll n;
struct edge{int u,v,w;bool operator <(const edge &b)const{return w<b.w;}
}e[N];
map<PII,int>mp;
int fa[N];
int fnd(int x){return fa[x]==x?x:fa[x]=fnd(fa[x]);
}
void solve(){int m,q; cin >> n >> m >> q;int sq=sqrt(n);for(int i=1;i<=m;i++){int u,v,w; cin >> u >> v >> w;e[i]={u,v,w};mp[{u,v}]=w;mp[{v,u}]=w;}sort(e+1,e+m+1);while(q--){int k; cin >> k;vector<int>ve;vector<edge>ed;unordered_map<int,bool>vi;for(int i=1;i<=k;i++){int s; cin >> s;ve.push_back(s);vi[s]=1;fa[s]=s;}if(k<=sq){for(int i=0;i<k;i++){for(int j=i+1;j<k;j++) if(mp.count({ve[i],ve[j]}))ed.push_back({ve[i],ve[j],mp[{ve[i],ve[j]}]});}sort(ed.begin(),ed.end());ll cnt=0,sum=0;for(auto t:ed){int u=fnd(t.u),v=fnd(t.v);if(u==v) continue;fa[u]=v;sum+=t.w;if(++cnt==k-1) break;}if(cnt==k-1) cout << sum << "\n";else cout << "-1\n";}else{ll cnt=0,sum=0;for(int i=1;i<=m;i++) if(vi.count(e[i].u) && vi.count(e[i].v)){int u=fnd(e[i].u),v=fnd(e[i].v);if(u==v) continue;fa[u]=v;sum+=e[i].w;if(++cnt==k-1) break;}if(cnt==k-1) cout << sum << "\n";else cout << "-1\n";}}
}
补 24牛客多校 二 I 牛客传送门
思路:p【num】【0/1】表示数字 num的左右区间下标
dp【num】【i】表示数字为num,考虑到第 i个数的最大贡献(i <= p【num】【1】
处理大区间时,需考虑其间的小区间贡献,将区间按长度从小到大排序,对每个区间进行遍历,若遇到包含在内的小区间,对是否转移取max,O(n^2)的复杂度
代码如下:
const int N=2e5+10,M=210;
const int INF=0x3f3f3f3f;
const int mod=998244353;
ll n;
int a[3030*2],p[3030][2];
int id[3030];
ll dp[3030][3030*2];
void solve(){cin >> n;for(int i=0;i<=n;i++) id[i]=i;a[1]=0,p[0][0]=1;for(int i=2;i<=n*2+1;i++){cin >> a[i];!p[a[i]][0]?p[a[i]][0]=i:p[a[i]][1]=i;}a[n*2+2]=0,p[0][1]=n*2+2;n++;sort(id,id+n,[&](int a,int b){return p[a][1]-p[a][0]<p[b][1]-p[b][0];});
// for(int i=1;i<=n;i++) cout << a[i] << " \n"[i==n]; //注意下标是到n*2for(int num=0;num<n;num++){int x=id[num];dp[x][p[x][0]]=x;for(int i=p[x][0]+1;i<=p[x][1];i++){dp[x][i]=dp[x][i-1]+x; //默认贡献为xif(p[a[i]][0]>p[x][0] && i==p[a[i]][1])dp[x][i]=max(dp[x][p[a[i]][0]-1]+dp[a[i]][i],dp[x][i]);}}cout << dp[0][n*2];
}
星期三:
学了下康托展开,可用来求全排列的编号,树状数组优化后复杂度为O(nlogn)
逆康托可以把编号转为全排列(编号的数据范围需在 20!内,21!会爆 ull
学这个的目的主要是掌握逆康托,如果遇到了可暴力全排列的题且不知正解,可以试着用随机数加上逆康托,生成一个随机的全排列,然后开始暴力枚举一定次数,有极小概率撞上答案,不过此为走投无路之举,且有很大的局限性,慎用
贴个n^2板子:
const int N=2e6+10,M=210;
const int INF=0x3f3f3f3f;
const int mod=998244353;
ll n;
ull fac[N];
ll kt(vector<int> ve){ll res=0;int sz=ve.size();unordered_map<int,bool>vi;for(int i=0;i<sz;i++){int cnt=0;for(int j=1;j<ve[i];j++) if(!vi.count(j)) cnt++; //可用树状数组优化(res+=1ll*cnt*fac[sz-i-1]%mod)%=mod;vi[ve[i]]=1;}return ++res%mod;
}
vector<int> rev_kt(ull k,int len){ //编号和排列长度vector<int>ans;k--;vector<int>ve;for(int i=1;i<=len;i++) ve.push_back(i);for(int i=1;i<=len;i++){int t=k/fac[len-i];ans.push_back(ve[t]);ve.erase(ve.begin()+t);k%=fac[len-i];}return ans;
}
void solve(){cin >> n;fac[0]=1;for(int i=1;i<=n;i++) fac[i]=fac[i-1]*i%mod;vector<int>ve;for(int i=1;i<=n;i++){int x; cin >> x;ve.push_back(x);}cout << kt(ve);
}
星期四:
补 24牛客多校 二 G 牛客传送门
题意:定义一集合为好集合,需满足元素乘积为平方数即 x^2,此集合权值即为 x。给一数集,问所有为好集合的子集的权值和是多少
贴个dalao题解:2024牛客暑期多校训练营2 - Luckyblock - 博客园 (cnblogs.com)
思路:由平方数考虑到质因子分解,值域1000以内,若存在大于31的质因子,则必定只有一个,且31以内的质数也只有11个,可以状压处理,将所有数分解,若不存在大质数则分解后为1,若存在即为大质数,按此分组,对每组进行转移
如何分解:对于每个数,我们要知道它的小质数的奇偶状态,但除此之外,还要处理出它的偶数个质因子对答案的贡献,即处理为pair值,再分组处理
dp【i】【mask】【0/1】表示考虑到每组第 i个数,小质数奇偶状态为mask,选了 偶/奇个大质数
对于不存在大质数的数,只需关注小质数奇偶状态,第三维暂时用不上,对于每个【ma,v】, 枚举状态进行转移,注意答案除了需乘v外,还需乘上两状态同奇的质数
对于存在大质数的数,按大质数 p分组进行转移,因为答案不断累加,到了下一组后,之前的答案仍保留,不过需要清除选了奇数个p‘(p’<p 的状态的值。接下来仍是枚举状态转移,注意每当 p选了偶数个,dp【now^1】【mask】【0】就需乘上 p
代码如下:
const int N=2e6+10,M=210;
const int INF=0x3f3f3f3f;
const int mod=1e9+7;
ll n;
ll dp[2][1<<11][2];
vector<PII>ve[1010];
int p[11]={2,3,5,7,11,13,17,19,23,29,31};
ll cal(int ma1,int ma2){ll res=1;for(int i=0;i<11;i++) if((ma1&ma2)&1<<i) (res*=p[i])%=mod;return res;
}
void solve(){cin >> n;for(int i=1;i<=n;i++){int x; cin >> x;ll xma=0,v=1;for(int j=0;j<11;j++){while(x%p[j]==0){if(xma&1<<j) v*=p[j];xma^=(1<<j);x/=p[j];}}ve[x].push_back({xma,v});}int now=0;dp[0][0][0]=1;for(auto [ma,v]:ve[1]){for(int mask=0;mask<1<<11;mask++) dp[now^1][mask][0]=dp[now][mask][0];for(int mask=0;mask<1<<11;mask++){int nmask=mask^ma;(dp[now^1][nmask][0]+=dp[now][mask][0]*v%mod*cal(mask,ma)%mod)%=mod;}now^=1;}for(int val=32;val<=1000;val++) if(!ve[val].empty()){for(int mask=0;mask<1<<11;mask++) dp[now][mask][1]=0;for(auto [ma,v]:ve[val]){for(int mask=0;mask<1<<11;mask++)dp[now^1][mask][1]=dp[now][mask][1],dp[now^1][mask][0]=dp[now][mask][0];for(int mask=0;mask<1<<11;mask++){int nmask=mask^ma;(dp[now^1][nmask][0]+=dp[now][mask][1]*v%mod*cal(mask,ma)%mod*val%mod)%=mod;(dp[now^1][nmask][1]+=dp[now][mask][0]*v%mod*cal(mask,ma)%mod)%=mod;}now^=1;}}cout << (dp[now][0][0]-1+mod)%mod;
}
星期五:
补 24牛客多校 三 A 牛客传送门
思路:最少的需要从右往左的趟数为 s= ( n-r ) / ( r-l )向上取整,最后一趟载 r个人,每个来回能有 r-l个人过河。把每个人的体力数处理为能趟一来回的次数 a即a = ( h-1 )/2。
最少要往回趟 s次,每次载 l个人,在贪心即每次运输体力最高的人的前提下,所有人 a的总和最少需要 s*l,若一个人的 a大于 s,那么超出的部分没有意义,所以计算总和时 a需要和 s取min,再和 s*l进行比较
代码如下:
const int N=2e6+10,M=210;
const int INF=0x3f3f3f3f;
const int mod=1e9+7;
ll n;
ll a[N];
void solve(){int l,r; cin >> n >> l >> r;ll s=(n-r+r-l-1)/(r-l),cp=0;for(int i=1;i<=n;i++){int h; cin >> h;a[i]=(h-1)/2;cp+=min(a[i],s);}if(cp>=s*l) cout << "YES";else cout << "NO";
}
恐怖故事,摸了两天半鱼,发现已经星期一了
这篇关于24.9.1(康托展开)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!