[题解][test05]2024/11/21 模拟赛 / 2023牛客OI赛前集训营-提高组(第二场) A~B

整套都是牛客的原题所以就不设密码了(
原题页面:https://ac.nowcoder.com/acm/contest/65193
Statements & Solution:https://www.luogu.com.cn/problem/U507206
Solution:https://www.nowcoder.com/discuss/540225827162583040

\(60+30+20+20=130\)

每日挂分之T2线段树不开\(4\)倍+\(10^6\)数量级输入不关同步流,\(\bf\colorbox{MidnightBlue}{\texttt{\color{White}{TLE}}}\ \ 100\to 30\)

A

赛时光想着\(f[i][j]\)表示\(i\)个元素选\(j\)个的答案,磕了半天没想出来。

实际上这道题应该从值域为\(n\)这一特征入手,这子集之和最大是\(\frac{n(n+1)}{2}\)

因此我们考虑枚举子集之和\(x\),用dp求出有多少种选法能达到这个子集和\(y\)。答案即为所有\(x^y\)相乘,用快速幂优化一下。

但是\(y\)可能非常大,但它作为指数不能直接取模。我们可以利用欧拉定理(其实就是费马小定理的推广)\(a^{\varphi(m)}\equiv1\pmod n\),将指数对\(\varphi(998244353)=998244352\)取模即可。

时间复杂度\(O(n^3)\)

点击查看代码
#include<bits/stdc++.h>
#define int long long
#define N 202
#define mod 998244353
using namespace std;
int n,f[N][N*N]={1},ans=1;
int qp(int a,int b){
int koishi=1;
while(b){
if(b&1) koishi=koishi*a%mod;
a=a*a%mod,b>>=1;
}
return koishi;
}
signed main(){
cin>>n;
for(int i=1;i<=n;i++){
for(int j=0;j<=n*(n+1)/2;j++){
f[i][j]=f[i-1][j];
if(j>=i) f[i][j]=(f[i][j]+f[i-1][j-i])%(mod-1);
}
}
for(int i=1;i<=n*(n+1)/2;i++) ans=ans*qp(i,f[n][i])%mod;
cout<<ans<<"\n";
return 0;
}

B

原题:P3488 [POI2009] LYZ-Ice Skates

我?赛时切紫?真的假的?!

假的 因为这个或那个的原因传奇地挂了\(70\)分,本来是可以切的。。。学到的:用cin一定要关同步流,线段树开\(4\)倍。


我们把每个住户选择的范围\([l,r]\)看作线段。

有解\(\iff\)对于所有区间,都有\(\bf x\le len\times k\)。其中\(\bf len\)是该区间的长度,\(\bf x\)是该区间覆盖的线段数。

  • 充分性:\(len\times k\)就是这个区间内的房间数量,所以如果该区间内的住户数量\(>\)房间数量,那么房间是不够用的。
  • 必要性:显然,无解\(\iff\)存在长度为\(d+1\)的区间不合法,那么有解\(\iff\)所有长度为\(d+1\)的区间都合法。
    在有解时考虑最极端的情况,即最大化\(x\)。显然此时需要\(d=0\),且所有长度为\(d+1\)的区间都放满了\(k\)条线段,那么总共覆盖的线段数量\(x=(len-d)\times k=len\times k\)。在有解的情况下\(x\)最大取到\(len\times k\),所以\(x\le len\times k\Rightarrow\)有解。

于是我们只需要判断该结论是否成立即可。

由于区间等长,我们简化一下,只统计区间左端点,判断是否存在区间\([l,r]\)使得\(x\le (len+d)\times k\),其中\(len=r-l+1\)\(x\)\([l,r]\)覆盖的左端点个数。

这样还是不太容易看,我们移一下项:\(x-len\times k\le d\times k\)

其中\(d\times k\)是常数,所以我们把\((x-len\times k)\)看作一个整体扔进线段树维护即可,具体来说,将线段树叶子节点初值赋为\(-k\)即可。这种把位置相关量看作一个整体来维护的技巧之前洛谷比赛有过:P11157 【MX-X6-T3】さよならワンダーランド ~ 题解

由于只要存在\((x-len\times k)>d\times k\)就是不合法的。所以我们需要想办法维护出那个\((x-len\times k)\)最大的区间来与\(d\times k\)进行比较。所以我们用线段树来维护一个最大连续子段和,模板题 P4513 小白逛公园,转移并不难理解,可以去看下洛谷的题解。

这样求出最大连续子段和,判定答案并输出即可。时间复杂度\(O(n\log n)\)

点击查看代码
#include<bits/stdc++.h>
#define lc(x) ((x)<<1)
#define rc(x) ((x)<<1|1)
#define int long long
#define N 500010
using namespace std;
int n,m,k,d,sum[N<<2],maxx[N<<2],lmax[N<<2],rmax[N<<2];
void update(int x){
sum[x]=sum[lc(x)]+sum[rc(x)];
maxx[x]=max({maxx[lc(x)],maxx[rc(x)],rmax[lc(x)]+lmax[rc(x)]});
lmax[x]=max({lmax[lc(x)],sum[lc(x)]+lmax[rc(x)]});
rmax[x]=max({rmax[rc(x)],sum[rc(x)]+rmax[lc(x)]});
}
void build(int x,int l,int r){
if(l==r){
sum[x]=-k;
return;
}
int mid=(l+r)>>1;
build(lc(x),l,mid),build(rc(x),mid+1,r);
update(x);
}
void chp(int a,int v,int x,int l,int r){
if(l==r){
sum[x]+=v;
lmax[x]=rmax[x]=maxx[x]=max(0ll,sum[x]);
return;
}
int mid=(l+r)>>1;
if(a<=mid) chp(a,v,lc(x),l,mid);
else chp(a,v,rc(x),mid+1,r);
update(x);
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin>>n>>m>>k>>d;
build(1,1,n-d);
int x,y;
while(m--){
cin>>x>>y;
chp(x,y,1,1,n-d);
if(maxx[1]>d*k) cout<<"NO\n";
else cout<<"YES\n";
}
return 0;
}

C

目前全网只能找到原比赛页面的题解了,但是看不懂,std也挺玄学的,\(f\)的含义都没搞懂……

先把自己的暴力和照着std写的代码放上来,等弄懂了会更新。

暴力的做法就是二进制枚举每个元素是否被选,然后先判断是否连通,再看连通块是否满足\(m\)个约束条件。

暴力
#include<bits/stdc++.h>
#define int long long
#define N 2010
#define M 25
using namespace std;
struct Limit{int u,v;}lim[M];
int n,m,v[N],ans,dep[N],koishi[N],idx,V;
bitset<N> sel[N];
vector<int> G[N];
void satori(int u,int fa){
dep[u]=dep[fa]+1;
for(int i:G[u]) satori(i,u);
}
void dfs(int u){
koishi[++idx]=u,V+=v[u];
for(int i:G[u]) if(sel[i]==1) dfs(i);
}
signed main(){
cin>>n>>m;
for(int i=1;i<=n;i++) cin>>v[i];
for(int i=1,s,v;i<=n;i++){
cin>>s;
while(s--) cin>>v,G[i].emplace_back(v);
}
satori(1,0);
for(int i=1;i<=m;i++) cin>>lim[i].u>>lim[i].v;
for(int i=(1<<n)-1;i;i--){
int mindep=INT_MAX,p=-1,pc=0;
for(int j=1;j<=n;j++) sel[j]=(i>>(j-1))&1,pc+=(sel[j]==1);
for(int j=1;j<=n;j++) if(sel[j]==1&&dep[j]<mindep) mindep=dep[j],p=j;
idx=V=0,dfs(p);
if(idx!=pc) continue;
bool f=1;
for(int j=1;j<=m;j++){
for(int k=1;k<idx;k++){
if((koishi[k]==lim[j].u&&koishi[k+1]==lim[j].v)||(koishi[k]==lim[j].v&&koishi[k+1]==lim[j].u)){
f=0;
break;
}
}
if(f==0) break;
}
if(f) ans=max(ans,V);
}
cout<<ans<<"\n";
return 0;
}
STD
#include<bits/stdc++.h>
#define int long long
#define N 100010
#define M 52//开2倍,因为无向
using namespace std;
int n,m,a[N],num[N],idx,ans,f[N][M];
struct t_edge{int u,v;}dat[M];
vector<int> G[N];
bitset<N> vis;
bitset<M> mp[M];
void dfs(int u){
f[u][num[u]]=a[u];
for(int i:G[u]){
dfs(i);
int maxx=LLONG_MIN;
for(int j=0;j<=idx;j++) if(!mp[j][num[i]]) maxx=max(maxx,f[u][j]);
for(int j=0;j<=idx;j++) f[u][j]=max(f[u][j],f[i][j]+maxx);
}
for(int i=0;i<=idx;i++) ans=max(ans,f[u][i]);
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(nullptr),cout.tie(nullptr);
cin>>n>>m;
for(int i=1;i<=n;i++) cin>>a[i];
for(int i=1,s,v;i<=n;i++){
cin>>s;
while(s--) cin>>v,G[i].push_back(v);
}
for(int i=1,u,v;i<=m;i++){
cin>>u>>v;
dat[i]={u,v};
vis[u]=vis[v]=1;
}
for(int i=1;i<=n;i++) if(vis[i]) num[i]=++idx;
for(int i=1;i<=m;i++){
int u=num[dat[i].u],v=num[dat[i].v];
mp[u][v]=mp[v][u]=1;
}
memset(f,-0x3f,sizeof f),dfs(1);
for(int i=1;i<=n;i++){
for(int j=0;j<=idx;j++){
cout<<i<<" "<<j<<" : "<<f[i][j]<<"\n";
}
}
cout<<ans<<"\n";
return 0;
}

D

这个有空再看。\(n\le 20\)的暴力思路就是将\(01\)串压成一个数,先二进制枚举问号处填什么,再对于每一种填法,跑DFS,记录能跑到的状态种数,累加答案。

暴力
#include<bits/stdc++.h>
#include<ext/pb_ds/assoc_container.hpp>
#include<ext/pb_ds/hash_policy.hpp>
#define N 514//無意識
#define mod 1000000007
using namespace std;
using namespace __gnu_pbds;
int n,un[N],idx,num,ans;
string s;
gp_hash_table<int,bool> vis;
bool getp(int x,int pos){return (x>>pos)&1;}
void dfs(int x){
if(vis[x]) return;
vis[x]=1;
for(int i=2;i<n;i++){
if(!getp(x,i-2)&&getp(x,i-1)&&getp(x,i)) dfs((x|(1<<(i-2)))&(~(1<<i)));
else if(getp(x,i-2)&&getp(x,i-1)&&!getp(x,i)) dfs((x|(1<<i))&(~(1<<(i-2))));
}
}
void solve(int x){
vis.clear();
dfs(x);
ans=(ans+vis.size())%mod;
}
signed main(){
cin>>n>>s;
for(int i=0;i<n;i++){
if(s[i]=='?') un[++idx]=i;
else if(s[i]=='1') num|=(1<<i);
}
for(int i=(1<<idx)-1;~i;i--){
int tnum=num;
for(int j=0;j<idx;j++)
if(getp(i,j)) tnum|=(1<<un[j+1]);
solve(tnum);
}
cout<<ans<<"\n";
return 0;
}
STD
#include <bits/stdc++.h>
using namespace std;
#define lep(i, l, r) for(int i = (l); i <= (r); i ++)
#define rep(i, l, r) for(int i = (l); i >= (r); i --)
const int N = 500 + 5;
const int P = 1e9 + 7;
inline int mod(int x) { return x + (x >> 31 & P); }
inline void pls(int &x, int y) { x = mod(x + y - P); }
int power(int x, int k) {
int res = 1;
while(k) {
if(k & 1) res = 1ll * res * x % P;
x = 1ll * x * x % P; k >>= 1;
} return res;
}
int n;
int F[N][N][2], G[N][N][2];
char str[N];
int fac[N], ifac[N];
inline int C(int x, int y) {
if(x < 0 || y < 0 || x < y) return 0;
return 1ll * fac[x] * ifac[y] % P * ifac[x - y] % P;
}
int main() {
ios :: sync_with_stdio(false); cin.tie(0); cout.tie(0);
cin >> n; cin >> (str + 1);
auto f = F;
auto g = G;
f[0][0][0] = 1;
lep (i, 1, n) {
memset(g, 0, sizeof(G));
lep (j, 0, i) lep (k, 0, i) lep (op, 0, 1) if(f[j][k][op]) {
char ch = str[i];
if(ch == '0' || ch == '?')
pls(g[j][k + 1][0], f[j][k][op]);
if(ch == '1' || ch == '?') {
if(op == 1) pls(g[j + 1][k][0], f[j][k][op]);
else pls(g[j][k][1], f[j][k][op]);
}
}
swap(f, g);
}
fac[0] = 1;
lep (i, 1, n) fac[i] = 1ll * fac[i - 1] * i % P;
ifac[n] = power(fac[n], P - 2);
rep (i, n - 1, 0) ifac[i] = 1ll * ifac[i + 1] * (i + 1) % P;
int ans = 0;
lep (i, 0, n) lep (j, 0, n) if(f[i][j][0] || f[i][j][1]) {
int res = mod(f[i][j][0] + f[i][j][1] - P);
ans = (ans + 1ll * res * C(i + j, i)) % P;
}
printf("%d\n", ans);
return 0;
}
posted @   Sinktank  阅读(21)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效
2025-3-6 6:10:34 TOP-BOTTOM-THEME
Enable/Disable Transition
Copyright © 2023 ~ 2024 Sinktank - 1328312655@qq.com
Illustration from 稲葉曇『リレイアウター/Relayouter/中继输出者』,by ぬくぬくにぎりめし.
点击右上角即可分享
微信分享提示