2026 2.23 - 2026 3.1 日做题题解
P8166 [eJOI 2021] Kpart / 背包 dp
考虑对于每一个区间判断是否为平衡的,那么设 \(f_{i,j}\) 表示前 \(i\) 个位置且选择的值为 \(j\) 的最大左端点,利用类似背包的转移即可。
然后直接判断如果 \(f_{i,x}\ge j\) 那么 \([j,i]\) 区间合法,其中 \(x\) 为区间和的一半。
#include<bits/stdc++.h>
#define int long long
using namespace std;
inline int read(){
char c=getchar();
int ans=0,f=1;
while(c<48||c>57) (c==45?f=-1:1),c=getchar();
while(c>=48&&c<=57) ans=(ans<<1)+(ans<<3)+(c^48),c=getchar();
return ans*f;
}
const int N=1010,M=1e5+10;
int a[N],vis[M],n,s[N],f[M];
inline void solve(){
n=read();
for (int i=1;i<=n;i++) a[i]=read(),s[i]=s[i-1]+a[i];
for (int i=1;i<=n;i++) vis[i]=1;
for (int i=1;i<=(s[n]>>1);i++) f[i]=0;
for (int i=1;i<=n;i++){
for (int j=(s[n]>>1);j>=a[i];j--) f[j]=max(f[j],f[j-a[i]]);
f[a[i]]=i;
for (int j=1;j<=i;j++) vis[i-j+1]&=((s[i]-s[j-1])%2==0&&f[(s[i]-s[j-1])>>1]>=j);
}
vector<int>anss;
for (int i=1;i<=n;i++) if (vis[i]) anss.push_back(i);
printf("%lld ",(int)anss.size());for (auto i:anss) printf("%lld ",i);puts("");
}
main(){
int T=read();
while(T--) solve();
return 0;
}
P4602 [CTSC2018] 混合果汁 题解 / 二分 + 主席树
首先考虑将美味度从大到小排序。
考虑二分美味度,我们要做的就是在体积给定的情况下,使得价格最小,显然我们可以按照价格从小到大贪心。
考虑对每一个节点维护线段树,下标为价格,存储这个价格的体积和,使用主席树可以轻松维护。
然后在给定体积的情况下,可以类似线段树二分的方法,求出最小的代价,做完了。
#include<bits/stdc++.h>
#define int long long
#define double long double
using namespace std;
inline int read(){
char c=getchar();
int f=1,ans=0;
while(c<48||c>57) f=(c==45?f=-1:1),c=getchar();
while(c>=48&&c<=57) ans=(ans<<1)+(ans<<3)+(c^48),c=getchar();
return ans*f;
}
const int N=1e5+10;
int n,m,root[N],sum1[N*50],sum2[N*50],ls[N*50],rs[N*50],idx;
struct node{
int d,p,l;
bool operator <(const node &x) const{return d>x.d;}
}a[N];
#define lc ls[k]
#define rc rs[k]
int change(int k,int l,int r,int x,int y){
sum1[++idx]=sum1[k]+y,sum2[idx]=sum2[k]+x*y,ls[idx]=ls[k],rs[idx]=rs[k];k=idx;
if (l==r) return k;
int mid=l+r>>1;
if (x<=mid) lc=change(lc,l,mid,x,y);else rc=change(rc,mid+1,r,x,y);
return k;
}
int ask(int k,int l,int r,int x){
if (l==r) return l*x;
int mid=l+r>>1;
if (sum1[lc]>=x) return ask(lc,l,mid,x);else return sum2[lc]+ask(rc,mid+1,r,x-sum1[lc]);
}
main(){
n=read(),m=read();
for (int i=1;i<=n;i++) a[i]={read(),read(),read()};sort(a+1,a+n+1);
for (int i=1;i<=n;i++) root[i]=change(root[i-1],1,N-1,a[i].p,a[i].l);
while(m--){
int x=read(),y=read();
int l=1,r=n,ans=-1;
while(l<=r){
int mid=l+r>>1;
if (ask(root[mid],1,N-1,y)<=x&&sum1[root[mid]]>=y) ans=a[mid].d,r=mid-1;else l=mid+1;
}
printf("%lld\n",ans);
}
return 0;
}
P7609 [THUPC 2021] 游戏
首先容易发现,他们在玩的就是 NIM 游戏。
那么实际上就是要数满足条件且异或和不为 \(0\) 的个数。
考虑正难则反,计算总数减去异或和为 \(0\) 的数。
总数直接容斥一下即可,钦定若干个点超过 \(a_i\)。
直接 \(O(2^m m)\) 计算即可。
考虑计算后一部分。
设 \(f_{i,S,j}\) 表示计算了 \(i\) 个高位,\(m\) 个数是否顶着高位的状态为 \(S\),而且低位进位了 \(j\)(上一位 \(m\) 个数的总和对这一位的贡献)。
那么显然 \(f_{60,2^m-1,0}=1\)。
转移的话直接枚举这一位会是什么数(注意 \(1\) 的个数要为偶数使得异或和为 \(0\))就是 \(O(4^m m \log n)\),显然无法通过。
但是我们发现,这一位顶着高位的转态肯定是上一位子集,那么考虑枚举子集,然后找到能填 \(1\) 的位置,组合数计算一下即可。
#include<bits/stdc++.h>
#define int long long
#define double long double
using namespace std;
inline int read(){
char c=getchar();
int f=1,ans=0;
while(c<48||c>57) f=(c==45?f=-1:1),c=getchar();
while(c>=48&&c<=57) ans=(ans<<1)+(ans<<3)+(c^48),c=getchar();
return ans*f;
}
const int mod=998244353;
inline void add(int &x,int y){x=(x+y)%mod;}
void exgcd(int a,int b,int &x,int &y){if (b==0) x=1,y=0;else exgcd(b,a%b,y,x),y-=a/b*x;}
inline int inv(int a){int x,y;exgcd(a,mod,x,y);return (x%mod+mod)%mod;}
int n,m,a[20],f[62][1<<10][15],c[20][20];
vector<int>tmp[1<<10];
main(){
n=read(),m=read();
for (int i=1;i<=m;i++) a[i]=read();
c[0][0]=1;for (int i=1;i<=m;i++){c[i][0]=1;for (int j=1;j<=i;j++) c[i][j]=(c[i-1][j]+c[i-1][j-1])%mod;}
for (int i=0;i<(1<<m);i++) for (int j=i;;j=(j-1)&i){tmp[i].push_back(j);if (j==0) break;}
int ans=0;
f[60][(1<<m)-1][0]=1;
for (int i=59;i>=0;i--) for (int s1=0;s1<(1<<m);s1++) for (auto s2:tmp[s1]){
bool flag=0;
for (int k=0;k<m;k++) if (((s1>>k)&1)&&((a[k+1]>>i)&1^1)&&((s2>>k)&1^1)) flag=1;
if (flag) continue;
int sum1=0,sum2=0;
for (int k=0;k<m;k++) if (((s1>>k)&1^1)||(((s1>>k)&1)&&((s2>>k)&1)&&((a[k+1]>>i)&1))) sum1++;
for (int k=0;k<m;k++) if (((s1>>k)&1)&&((s2>>k)&1)&&((a[k+1]>>i)&1)) sum2++;
int tmp=sum2;sum1-=sum2,sum2&=1;
while(sum2<=sum1){
for (int j=0;j<=m;j++){
int cnt=sum2+tmp+j;
if ((cnt&1)^((n>>i)&1)^1) add(f[i][s2][j],f[i+1][s1][cnt>>1]*c[sum1][sum2]%mod);
}
sum2+=2;
}
}
for (int i=0;i<(1<<m);i++) add(ans,f[0][i][0]);
ans=(mod-ans)%mod;
for (int i=0;i<(1<<m);i++){
int cnt=n;
for (int j=0;j<m;j++) if ((i>>j)&1) cnt-=a[j+1]+1;
int x=cnt+m-1,y=m-1,sum=1;
if (x<=0) sum=0;else for (int i=1;i<=y;i++) sum=(__int128)sum*(x-i+1)%mod*inv(i)%mod;
if (__builtin_popcount(i)&1) add(ans,mod-sum);else add(ans,sum);
}
cout <<ans;
return 0;
}
AGC014E Blue and Red Tree
考虑倒序处理操作,我们发现最后一次操作需要满足存在一个红蓝边在同一个位置,然后考虑将其合并在一起,然后再找一个红蓝边同时存在的位置即可。
利用队列和启发式合并即可,有一些细节,具体可以看代码。
#include<bits/stdc++.h>
#define int long long
#define pii pair<int,int>
using namespace std;
inline int read(){
char c=getchar();
int ans=0,f=1;
while(c<48||c>57) (c==45?f=-1:1),c=getchar();
while(c>=48&&c<=57) ans=(ans<<1)+(ans<<3)+(c^48),c=getchar();
return ans*f;
}
const int N=1e5+10;
int n;
map<pii,int>mp;
queue<pii>q;
multiset<int>s[N];
inline void add(int x,int y){
if (x==y) return ;
if (x>y) swap(x,y);
if (++mp[{x,y}]==2) q.push({x,y});
s[x].insert(y),s[y].insert(x);
}
main(){
n=read();
for (int i=1;i<2*n-1;i++) add(read(),read());
int cnt=0;
while(cnt<n-1){
if (q.empty()) return 0&puts("NO");
pii x=q.front();q.pop();
if (!mp.count(x)) continue;
int u=x.first,v=x.second;
if (s[u].size()<s[v].size()) swap(u,v);
for (auto i:s[v]) mp[{min(i,v),max(i,v)}]=0,s[i].erase(s[i].find(v)),add(u,i);
if (s[v].size()>1) cnt++;s[v].clear();
}
puts("YES");
return 0;
}

浙公网安备 33010602011771号