L.网络预选赛
签到
cpp
char a[510][510];
void solve(){
int n,m;
cin>>n>>m;
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
cin>>a[i][j];
}
}
int ans=0;
for(int i=1;i<n;i++){
for(int j=1;j<m;j++){
if(a[i][j]=='c'&&a[i][j+1]=='c'&&a[i+1][j]=='p'&&a[i+1][j+1]=='c'){
ans++;
}
}
}
cout<<ans<<"\n";
}K.取石子游戏

- 先手取掉,则后面跟着后手取就行,先手必胜
- 否则随便取一个数都会变成情况一,先手必败
B.军训

- 很容易想到是排序好的数组
- 然后相同值的任意排
- 升序降序都可,要乘2,特殊考虑所有数相同
cpp
void solve(){
int n;
cin>>n;
vector<int> a(n+1),fac(n+1,1);
for(int i=1;i<=n;i++)fac[i]=fac[i-1]*i%mod;
map<int,int> cnt;
for(int i=1;i<=n;i++)cin>>a[i],cnt[a[i]]++;
sort(a.begin()+1,a.end());
int ans=1,res=0;
for(auto [x,y]:cnt)ans=ans*fac[y]%mod;
for(int i=1;i<=n;i++){
for(int j=i;j<=n;j++){
res+=a[j]-a[i];
}
}
cout<<res<<" "<<ans*(cnt.size()==1?1:2)%mod<<"\n";
}D.编码器-解码器

- 定义为当前字符串能构成的方案
- 迭代一次,分类讨论
- 不使用中间的元素
- 继承左右两边有
- 左右两边分别贡献一部分
- 使用中间的元素,
- 那么更新以为右端点的部分,以为左端点的部分
- 以为右端点和以为左端合并
- 不使用中间的元素
cpp
void solve(){
string s,t;
cin>>s>>t;
int n=s.size(),m=t.size();
s=" "+s,t=" "+t;
vector dp(m+1,vector<int>(m+1,0));
for(int i=1;i<=n;i++){
auto ndp=dp;
for(int l=1;l<=m;l++){
for(int r=l;r<=m;r++){
ndp[l][r]=ndp[l][r]*2%mod;
}
}
for(int len=2;len<=m;len++){
for(int l=1;l<=m-len+1;l++){
int r=l+len-1;
for(int k=l;k<r;k++)ndp[l][r]=(ndp[l][r]+dp[l][k]*dp[k+1][r]%mod)%mod;
}
}
for(int j=1;j<=m;j++){
if(s[i]!=t[j])continue;
ndp[j][j]=(ndp[j][j]+1)%mod;
for(int l=1;l<j;l++)ndp[l][j]=(ndp[l][j]+dp[l][j-1])%mod;
for(int r=j+1;r<=m;r++)ndp[j][r]=(ndp[j][r]+dp[j+1][r])%mod;
for(int l=1;l<j;l++){
for(int r=j+1;r<=m;r++){
ndp[l][r]=(ndp[l][r]+dp[l][j-1]*dp[j+1][r]%mod)%mod;
}
}
}
swap(dp,ndp);
}
cout<<dp[1][m]<<"\n";
}E.随机过程

- 对于第层的点,他们出现概率相同为
- 个字符串都没出现该节点的概率为,出现的概率为
- 因此期望节点为
cpp
int qpow(int a,int b){
a%=mod;
int ans=1;
for(;b;b>>=1){
if(b&1)ans=ans*a%mod;
a=a*a%mod;
}
return ans;
}
void solve(){
int n,m;
cin>>n>>m;
int pw=1,ans=1,res=1;
for(int i=1;i<=m;i++){
pw=pw*26%mod;
if(pw<=n)ans=(ans+pw)%mod;
else{
ans=(ans+(m-i+1)*n%mod)%mod;
break;
}
}
pw=1;
for(int i=1;i<=m;i++){
pw=pw*26%mod;
res+=(1-qpow(1-qpow(pw,mod-2)+mod,n)+mod)%mod*pw%mod;
res%=mod;
}
cout<<ans<<' '<<res<<'\n';
}J.找最小

- 考虑求出异或和,那么交换两个数相当于同时对异或和异或上
- 那么转变成一共有个值,选出若干个与异或,最小化
- 考虑使用线性基,从高到低按位考虑
- 直接跳过
- 如果当前线性基有数异或上,否则不变
- 一个1一个0的情况,假设那么异或上肯定更优,否则不用管
cpp
#include<bits/stdc++.h>
#include<ext/pb_ds/assoc_container.hpp>
#include<ext/pb_ds/tree_policy.hpp>
using namespace std;
using namespace __gnu_pbds;
#define int long long
#define TR tree<int,null_type,less<int>,rb_tree_tag,tree_order_statistics_node_update>
typedef pair<int,int> PII;
const int N=5e5+10,M=2e5+10;
// const int INF=1e18;
// const int mod=998244353;
// const int mod=1e9+7;
mt19937_64 rng(chrono::steady_clock::now().time_since_epoch().count());
int qpow(int a,int b,int mod){
int ans=1;
for(;b;b>>=1){
if(b&1)ans=ans*a%mod;
a=a*a%mod;
}
return ans;
}
struct LinearBasis{
vector<int> d;
int fail,mxb;
static constexpr int SIZE=32;
vector<int> nums;
LinearBasis(){
init();
}
void init(){
d.assign(SIZE,0);
fail=mxb=0;
nums.clear();
}
bool insert(int x){
for(int i=SIZE-1;i>=0;i--){
if(!(x>>i&1))continue;
mxb=max(mxb,i);
if(d[i])x^=d[i];
else{
d[i]=x;
return true;
}
}
fail++;
return false;
}
int query_max(int res=0){
for(int i=SIZE-1;i>=0;i--){
res=max(res,res^d[i]);
}
return res;
}
int query_min(int res=0){
if(!res)return fail?0:*min_element(d.begin(),d.begin()+mxb+1);
for(int i=SIZE-1;i>=0;i--){
res=min(res,res^d[i]);
}
return res;
}
} lb;
int dfs(int i,int x,int y){
if(i==-1)return max(x,y);
int l=x>>i&1,r=y>>i&1;
if(!lb.d[i]||(l==0&&r==0))return dfs(i-1,x,y);
if(l==1&&r==1)return dfs(i-1,x^lb.d[i],y^lb.d[i]);
if((l==1&&r==0&&x>y)||(l==0&&r==1&&x<y))return dfs(i-1,x^lb.d[i],y^lb.d[i]);
return dfs(i-1,x,y);
}
void solve(){
lb.init();
int n;
cin>>n;
int x=0,y=0;
vector<int> a(n+1),b(n+1);
for(int i=1;i<=n;i++)cin>>a[i],x^=a[i];
for(int i=1;i<=n;i++)cin>>b[i],y^=b[i],lb.insert(a[i]^b[i]);
cout<<dfs(31,x,y)<<"\n";
}
signed main(){
cin.tie(0)->sync_with_stdio(0);
int T=1;
cin>>T;
while(T--)solve();
return 0;
}G.疯狂星期六

- 首先计算出yyq的最大花费,然后限制其他人的花费都比它小
- 跑最大流,当能把菜品全部支付完,达到满流,是合法方案否则NO
- 不需要判断yyq流出的边是否满流,因为当合法时,必定可以减少其他人对菜品的流量,增加yyq的流量
cpp
#include<bits/stdc++.h>
#define int long long
using namespace std;
typedef pair<int,int> PII;
const int N=3010,M=1e5+10;
const int INF=1e18;
const int mod=998244353;
// const int mod=1e9+7;
mt19937_64 rng(chrono::steady_clock::now().time_since_epoch().count());
int n,m,S,T;//S为源点,T为汇点
int e[M],h[N],ne[M],idx;//链式前向星
int f[M],d[N],cur[N];//f存流量,d存层数,cur为弧优化,表示当前从哪条节点开始搜
void add(int a,int b,int c){
e[idx]=b,f[idx]=c,ne[idx]=h[a],h[a]=idx++;
e[idx]=a,f[idx]=0,ne[idx]=h[b],h[b]=idx++;//反向流量初始为0
}
bool bfs(){
queue<int> q;
memset(d,-1,sizeof d);
q.push(S);
d[S]=0,cur[S]=h[S];
while(!q.empty()){
auto u=q.front();
q.pop();
for(int i=h[u];~i;i=ne[i]){
int v=e[i];
if(d[v]==-1&&f[i]){
d[v]=d[u]+1;
cur[v]=h[v];//一开始所有边都可以搜
if(v==T)return true;
q.push(v);
}
}
}
return false;
}
int find(int u,int limit){//深搜,limit表示从源点流向u的最多能流的流,flow表示已经流向汇点多少流
if(u==T)return limit;
int flow=0;
for(int i=cur[u];~i&&flow<limit;i=ne[i]){//flow<limit优化很重要,否则易tle
int v=e[i];
cur[u]=i;//弧优化更新
if(d[v]==d[u]+1&&f[i]){
int t=find(v,min(f[i],limit-flow));
if(!t)d[v]=-1;//如果到达不了T,标记下次就不会走到
f[i]-=t,f[i^1]+=t,flow+=t;
}
}
return flow;
}
int dinic(){
int r=0,flow;
while(bfs())
while(flow=find(S,INF))r+=flow;
return r;
}
void solve(){
int n,m;
cin>>n>>m;
vector<int> a(n+1),V(n+1);
for(int i=1;i<=n;i++)cin>>a[i]>>V[i];
vector<array<int,3>> W(m+1);
int all=V[1];
int sum=0;
memset(h,-1,sizeof h);
for(int i=1;i<=m;i++){
cin>>W[i][0]>>W[i][1]>>W[i][2];
if(W[i][0]>W[i][1])swap(W[i][0],W[i][1]);
if(W[i][0]==1)all+=W[i][2];
sum+=W[i][2];
}
all=min(all,a[1]);
S=0,T=n+m+1;
add(S,1,all-V[1]);
for(int i=2;i<=n;i++){
int x=min(all-1,a[i])-V[i];
if(x<0){
cout<<"NO\n";
return;
}
add(S,i,min(all-1,a[i])-V[i]);
}
for(int i=1;i<=m;i++){
add(n+i,T,W[i][2]);
add(W[i][0],n+i,INF);
if(W[i][1]!=W[i][0])add(W[i][1],n+i,INF);
}
cout<<(dinic()==sum?"YES\n":"NO\n");
}
signed main(){
cin.tie(0)->sync_with_stdio(0);
int T=1;
// cin>>T;
while(T--)solve();
return 0;
}I.找行李

- 如果第个人选了行李,后面的人再选前面的行李没有意义
- 容斥,考虑分别计算最终答案大于等于的方案,那么能选的集合也能选
- 定义表示考虑了前个人,剩余个行李的方案,答案很容易转移,特殊地没有人选行李的方案不能统计进去
cpp
#include<bits/stdc++.h>
#include<ext/pb_ds/assoc_container.hpp>
#include<ext/pb_ds/tree_policy.hpp>
using namespace std;
using namespace __gnu_pbds;
#define int long long
#define TR tree<int,null_type,less<int>,rb_tree_tag,tree_order_statistics_node_update>
typedef pair<int,int> PII;
const int N=1e5+10,M=2e5+10;
const int INF=1e18;
const int mod=998244353;
// const int mod=1e9+7;
mt19937_64 rng(chrono::steady_clock::now().time_since_epoch().count());
int a[510],b[510],ans[510];
void solve(){
int n,m;
cin>>n>>m;
for(int i=1;i<=n;i++)cin>>a[i];
for(int i=1;i<=m;i++)cin>>b[i];
sort(a+1,a+n+1);
sort(b+1,b+m+1);
for(int i=0;i<=500;i++){
vector dp(n+1,vector<int>(m+1,0));
dp[0][0]=1;
for(int j=1;j<=n;j++){
int d=upper_bound(a+1,a+n+1,b[j]-i)-a-1;
int ld;
if(j==1)ld=0;
else ld=upper_bound(a+1,a+n+1,b[j-1]-i)-a-1;
// cout<<i<<" "<<j<<" "<<d<<"\n";
for(int k=0;k<=ld;k++){
int x=ld-k;
// cout<<"j="<<j<<" k="<<k<<" x="<<x<<" d="<<d<<" val="<<dp[j-1][k]<<"\n";
if(d-x>=0)dp[j][d-x]=(dp[j][d-x]+dp[j-1][k])%mod;
if(d-x-1>=0)dp[j][d-x-1]=(dp[j][d-x-1]+dp[j-1][k]*(d-x))%mod;
}
}
// cout<<dp[2][1]<<"\n";
int d=upper_bound(a+1,a+n+1,b[m]-i)-a-1;
for(int j=0;j<=d-1;j++){
ans[i]=(ans[i]+dp[n][j])%mod;
}
}
int res=0;
for(int i=0;i<=500;i++){
// cout<<ans[i]<<"\n";
if(i+1<=500)ans[i]-=ans[i+1];
res=(res+ans[i]*i)%mod;
}
cout<<res<<"\n";
}C.种树

- 每次可以选择两个点染色,因此考虑尽可能多地选择两个未涂的点
- 发现顺序并不重要
- 对于一个点,需要考虑的只有儿子未涂记为,儿子的儿子未涂记为
- 假设当前节点已经涂了或者,那么答案即为,此时节点必定已涂,如果剩一个儿子,那么向上传递2,否则为0
- 否则,如果,向上传递0
- 否则只剩下儿子和当前节点,那么如果未涂儿子是奇数,则可以刚好匹配完,否则剩一个当前节点,向上传递1
- 因为都是小于等于两个的未涂互相匹配,那么肯定会有与其他已涂的相交,保证了答案的合法性
cpp
void solve(){
int n,k;
cin>>n>>k;
vector<int> have(n+1);
for(int i=1;i<=k;i++){
int x;
cin>>x;
have[x]++;
}
vector<int> adj[n+1];
for(int i=1;i<n;i++){
int u,v;
cin>>u>>v;
adj[u].push_back(v);
adj[v].push_back(u);
}
if(n==k){
cout<<"0\n";
return;
}
int ans=0;
function<int(int,int)> dfs=[&](int u,int fa){
vector<int> cnt(3);
for(auto v:adj[u]){
if(v==fa)continue;
cnt[dfs(v,u)]++;
}
if(have[u]||cnt[2]){
ans+=cnt[2]+cnt[1]/2;
if(cnt[1]%2)return 2;
return 0;
}
if(cnt[1]==0)return 1;
ans+=(cnt[1]+1)/2;
if((cnt[1]+1)%2)return 1;
return 0;
};
if(dfs(1,0)!=0)ans++;
cout<<ans<<"\n";
}