你猜我为什么不写游记。
社团招新(club)
直接贪心即可。
#include<bits/stdc++.h>
#define Debug puts("-------------------------")
using namespace std;
const int N=1e5+5;inline int read(){int w=1,s=0;char c=getchar();for(;c<'0'||c>'9';w*=(c=='-')?-1:1,c=getchar());for(;c>='0'&&c<='9';s=s*10+c-'0',c=getchar());return w*s;
}
int T,n,a[N][5],id[N],cnt[5],ans; 
signed main(){
//	freopen("club.in","r",stdin);
//	freopen("club.out","w",stdout);T=read();while(T--){cnt[1]=cnt[2]=cnt[3]=0;n=read();ans=0;for(int i=1;i<=n;i++){int maxn=0;for(int j=1;j<=3;j++) a[i][j]=read(),maxn=max(maxn,a[i][j]);for(int j=1;j<=3;j++){if(maxn==a[i][j]){id[i]=j,cnt[j]++;break;}}ans+=maxn;}int mx=max({cnt[1],cnt[2],cnt[3]});if(mx>n/2){int o;for(int i=1;i<=3;i++) if(mx==cnt[i]) o=i;vector<int> vec;for(int i=1;i<=n;i++){if(id[i]==o){int se=0;for(int j=1;j<=3;j++) if(j!=o) se=max(se,a[i][j]);vec.push_back(a[i][o]-se);}}sort(vec.begin(),vec.end());for(int i=0;i<mx-n/2;i++) ans-=vec[i];}printf("%d\n",ans);}return 0;
}
道路修复(road)
根据 Kruskal 的过程不难发现在加上那 \(O(nk)\) 条边后原图的边只有原最小生成树上的边是有用的,因此有用的边数是 \(O(nk)\) 的。
然后 \(O(2^k)\) 枚举选择了哪些新点,然后跑 Kruskal 求出此时的最小生成树即可。
可以提前在外面排好 \(O(nk)\) 条边的顺序,这样枚举之后就不需要再排一遍序了。
\(O(2^knk\alpha(n))\),应该不是正解,但跑得挺快的。
code
#include<bits/stdc++.h>
#define Debug puts("-------------------------")
#define LL long long 
using namespace std;
const int N=1e4+100,M=1e6+5;inline int read(){int w=1,s=0;char c=getchar();for(;c<'0'||c>'9';w*=(c=='-')?-1:1,c=getchar());for(;c>='0'&&c<='9';s=s*10+c-'0',c=getchar());return w*s;
}
int n,m,k,fa[N],c[15],a[15][N],tot;
bool flag[15];
LL ans=LLONG_MAX;
struct Edge{ int u,v,w,op; } E[M],MST[N];
int get(int x){ return (x==fa[x])?(x):(fa[x]=get(fa[x])); }
bool cmp(Edge x,Edge y){ return x.w<y.w; }
void Kruskal(){for(int i=1;i<=n;i++) fa[i]=i;sort(E+1,E+m+1,cmp);for(int i=1;i<=m;i++){int x=E[i].u,y=E[i].v;if(get(x)==get(y)) continue;MST[++tot]=E[i],fa[get(x)]=get(y);}
}
void Init(){Kruskal();for(int i=1;i<=tot;i++) E[i]=MST[i];for(int i=1;i<=k;i++) for(int j=1;j<=n;j++) E[++tot]={n+i,j,a[i][j],i};sort(E+1,E+tot+1,cmp);
}
LL work(int S){LL res=0;for(int i=1;i<=k;i++) (S>>(i-1)&1)?(res+=c[i],flag[i]=true):(flag[i]=false);for(int i=1;i<=n+k;i++) fa[i]=i;for(int i=1;i<=tot;i++){if(!flag[E[i].op]) continue;int x=E[i].u,y=E[i].v,w=E[i].w;if(get(x)==get(y)) continue;fa[get(x)]=get(y),res+=w;}return res;
}
signed main(){
//	freopen("road.in","r",stdin);
//	freopen("road.out","w",stdout);double beg=clock();n=read(),m=read(),k=read();for(int i=1;i<=m;i++){int u=read(),v=read(),w=read();E[i]={u,v,w,0};}for(int i=1;i<=k;i++){c[i]=read();for(int j=1;j<=n;j++) a[i][j]=read();}Init();flag[0]=true;for(int S=0;S<(1<<k);S++) ans=min(ans,work(S));printf("%lld\n",ans);cerr << fixed << setprecision(3) << (double)(clock()-beg) << endl;return 0;
}
谐音替换(replace)
先求出询问串 \(t_1,t_2\) 的 LCP 和 LCS,不妨设 \(t_1=ACB,t_2=ADB\),那显然如果一对 \((s_1,s_2)\) 合法需要满足他俩中间不一样的部分也分别为 \(C,D\),不妨设 \(s_1=A'CB',s_2=A'DB'\),那么合法的另一个条件是 \(B'\) 是 \(B\) 的前缀,\(reverse(A')\) 是 \(reverse(A)\) 的前缀。
因此先用哈希(比如 map 套 vector)将所有 \(C,D\) 相同的二元组和询问分到同一类里,对每一类单独求解。在每一类中,对每个 \((s_1,s_2)\) 或 \((t_1,t_2)\) 将 \(A\) 倒序插入第一棵 Trie,将 \(B\) 正序插入第二棵 Trie,那么合法的条件可以转化为 \((t_1,t_2)\) 在两棵 Trie 上对应的节点均在 \((s_1,s_2)\) 对应的节点子树内,这是经典二维偏序,直接树状数组即可。
复杂度 \(O((L1+L2)|\sum| + (n+q)\log L)\)。
Tip:题目没保证 \(|t_1|=|t_2|\),注意特判。
code
#include<bits/stdc++.h>
#define Debug puts("-------------------------")
#define ULL unsigned long long 
#define PII pair<int,int>
#define fi first
#define se second 
#define mk make_pair
using namespace std;
const int N=2e5+5,M=5e6+5,p=13331;inline int read(){int w=1,s=0;char c=getchar();for(;c<'0'||c>'9';w*=(c=='-')?-1:1,c=getchar());for(;c>='0'&&c<='9';s=s*10+c-'0',c=getchar());return w*s;
}
ULL mi[M];
int n,q,ans[N],Ls[N],Rs[N],len1[N],Lt[N],Rt[N],len2[N];
string s[N][2],t[N][2];
ULL Hash(string &s,int l,int r){ULL H=0;for(int i=l;i<=r;i++) H=H*p+s[i];return H;
}
map<ULL,vector<PII> > mp;
void init(string &s,string &t,int &len,int &L,int &R,int op,int id){len=s.size();s=' '+s,t=' '+t;L=0,R=len+1;for(int j=1;j<=len;j++){if(s[j]==t[j]) L=j;else break;}for(int j=len;j>=1;j--){if(s[j]==t[j]) R=j;else break;}		if(L==len) return;int l=R-L+1;mp[Hash(s,L+1,R-1)*mi[l]+Hash(t,L+1,R-1)].push_back({op,id});
} 
namespace Solve{struct Trie{int ch[M][26],tot,dfn[M],siz[M],num;void Init(){for(int i=1;i<=tot;i++) memset(ch[i],0,sizeof ch[i]);tot=1,num=0;}int insert(string &s,int l,int r,int o){int p=1;for(int i=(o?r:l);o?(i>=l):(i<=r);o?(i--):(i++)){int c=s[i]-'a';if(!ch[p][c]) ch[p][c]=++tot;p=ch[p][c];}return p;}void dfs(int u){dfn[u]=++num,siz[u]=1;for(int i=0;i<=25;i++) if(ch[u][i]) dfs(ch[u][i]),siz[u]+=siz[ch[u][i]];}}T1,T2;PII ed1[N],ed2[N];struct BIT{int c[M];void init(int num){ for(int i=1;i<=num;i++)	c[i]=0; }int lowbit(int x){ return x&(-x); }void add(int i,int x,int num){	for(;i<=num;i+=lowbit(i)) c[i]+=x; }int ask(int i,int sum=0){for(;i;i-=lowbit(i)) sum+=c[i];return sum;}}Bit;struct P{ int op,x,y1,y2; }a[N*3];bool cmp(P x,P y){if(x.x!=y.x) return x.x<y.x;return x.op<y.op;}void work(vector<PII> vec){T1.Init(),T2.Init();for(PII o:vec){int op=o.fi,id=o.se;if(op==0) ed1[id]=mk(T1.insert(s[id][0],1,Ls[id],1),T2.insert(s[id][1],Rs[id],len1[id],0));else ed2[id]=mk(T1.insert(t[id][0],1,Lt[id],1),T2.insert(t[id][1],Rt[id],len2[id],0)); }T1.dfs(1),T2.dfs(1);int cnt=0;for(PII o:vec){int op=o.fi,id=o.se,u,v;if(op==0){u=ed1[id].fi,v=ed1[id].se;a[++cnt]={0,T1.dfn[u],T2.dfn[v],T2.dfn[v]+T2.siz[v]};a[++cnt]={-1,T1.dfn[u]+T1.siz[u],T2.dfn[v],T2.dfn[v]+T2.siz[v]};}else{u=ed2[id].fi,v=ed2[id].se;a[++cnt]={id,T1.dfn[u],T2.dfn[v],0};}}sort(a+1,a+cnt+1,cmp);int num=T2.num;Bit.init(num);for(int i=1;i<=cnt;i++){int op=a[i].op;if(op==0) Bit.add(a[i].y1,1,num),Bit.add(a[i].y2,-1,num);else if(op==-1) Bit.add(a[i].y1,-1,num),Bit.add(a[i].y2,1,num);else ans[op]=Bit.ask(a[i].y1);}}
}
signed main(){
//	freopen("replace.in","r",stdin);
//	freopen("replace.out","w",stdout);double beg=clock();ios::sync_with_stdio(false);cin.tie(0),cout.tie(0);mi[0]=1;for(int i=1;i<M;i++) mi[i]=mi[i-1]*p;cin>>n>>q;for(int i=1;i<=n;i++){cin>>s[i][0]>>s[i][1];init(s[i][0],s[i][1],len1[i],Ls[i],Rs[i],0,i);}for(int i=1;i<=q;i++){cin>>t[i][0]>>t[i][1];if(t[i][0].size()!=t[i][1].size()) continue;init(t[i][0],t[i][1],len2[i],Lt[i],Rt[i],1,i);		}for(pair<ULL,vector<PII> > o:mp) Solve::work(o.se);for(int i=1;i<=q;i++) printf("%lld\n",ans[i]);cerr << fixed << setprecision(3) << (double)(clock()-beg) << endl;return 0;
}
员工招聘(employ)
设 \(cnt_i\) 表示耐心值为 \(i\) 的人数,\(pre\) 是 \(cnt\) 的前缀和。
一个显然的 DP 状态是 \(f_{i,j}\) 表示前 \(i\) 个人,有 \(j\) 个人被拒绝了的方案数。但发现这样是转移不动的,因为比如当我们需要往第 \(i+1\) 放一个 \(c\le j\) 的人时,由于我们并不知道前 \(i\) 个人具体的耐心值情况,所以并不知道还可以放的人有多少。
因此我们不妨加一维 \(f_{i,j,k}\) 其中 \(k\) 表示前 \(i\) 个人中有 \(k\) 个人的耐心值 \(\le j\)。此时我们就知道,如果我要在 \(i+1\) 放一个耐心值 \(\le j\) 的人,那么总共就有 \(pre_j-k\) 的方案了。但是一个新的问题是,当第 \(i+1\) 个人被拒绝时,\(j\) 会变成 \(j+1\),此时我们就需要知道 \(c\le j+1\) 的人数了,但显然根据我们目前的状态,这个信息是不能直接推出来的。
解决方法其实也比较套路,我们考虑贡献延后计算,先不去确定那 \(i-k\) 个 \(c>j\) 的人的具体的耐心值,而是在 \(j\to j+1\) 时,再去分配有哪些人的 \(c=j+1\),具体的,考虑刷表法:
- \(s_{i+1}=1\) 且第 \(i+1\) 个人被录取,选一个 \(c>j\) 的人即可,但先不去确定他是谁:\(f_{i,j,k} \to f_{i+1,j,k}\)。
 - \(s_{i+1}=1\) 且第 \(i+1\) 个人不被录取,先从 \(pre_j-k\) 个人当中选一个 \(c\le j\) 的人,再枚举 \(l\) 表示那 \(i-k\) 个人当中有几个人的耐心值为 \(j+1\):\(f_{i,j,k}\times (pre_j-k) \times \binom{i-k}{l} \times \binom{cnt_{j+1}}{l} \times l! \to f_{i+1,j+1,k+l+1}\)。
 - \(s_{i+1}=0\) 此时无论填什么都会拒绝录取,因此转移跟 2 差不多,具体看代码。
 
看似状态数是 \(O(n^3)\),转移枚举 \(l\) 是 \(O(n)\) 的,但是注意到 \(l\le cnt_{j+1}\),因此当 \(i,k\) 确定时,对于所有 \(1\le j\le n\),\(l\) 的总枚举量为 \(O(n)\)。总复杂度为 \(O(n^3)\)。
code
#include<bits/stdc++.h>
#define Debug puts("-------------------------")
using namespace std;
const int N=5e2+5,mod=998244353;inline int read(){int w=1,s=0;char c=getchar();for(;c<'0'||c>'9';w*=(c=='-')?-1:1,c=getchar());for(;c>='0'&&c<='9';s=s*10+c-'0',c=getchar());return w*s;
}
int n,m,cnt[N],pre[N],fact[N],inv[N],f[N][N][N];
char s[N]; 
int C(int n,int m){ if(n<0||m<0||n<m) return 0;return 1ll*fact[n]*inv[m]%mod*inv[n-m]%mod; 
}
void Add(int &x,int y){ (x+=y)%=mod; }
signed main(){
//	freopen("employ.in","r",stdin);
//	freopen("employ.out","w",stdout);double beg=clock();fact[0]=inv[0]=inv[1]=1;for(int i=1;i<N;i++) fact[i]=1ll*fact[i-1]*i%mod;for(int i=2;i<N;i++) inv[i]=1ll*(mod-mod/i)*inv[mod%i]%mod;for(int i=2;i<N;i++) inv[i]=1ll*inv[i-1]*inv[i]%mod;scanf("%d%d%s",&n,&m,s+1);for(int i=1,x;i<=n;i++) scanf("%d",&x),cnt[x]++;pre[0]=cnt[0];for(int i=1;i<=n;i++) pre[i]=cnt[i]+pre[i-1];f[0][0][0]=1;for(int i=0;i<n;i++){for(int j=0;j<=i;j++){for(int k=0;k<=min(i,pre[j]);k++){if(!f[i][j][k]) continue;int val=f[i][j][k];if(s[i+1]=='1'){Add(f[i+1][j][k],val);  //通过,选一个耐心值 >j 的人 if(k<pre[j]){for(int l=0;l<=min(cnt[j+1],i-k);l++){  //不通过,选一个耐心值 <=j 的人,并决策之前哪些人的耐心值 =j+1 Add(f[i+1][j+1][k+l+1],1ll*val*(pre[j]-k)%mod*C(i-k,l)%mod*C(cnt[j+1],l)%mod*fact[l]%mod);}}} else{ //必定失败 for(int l=0;l<=min(cnt[j+1],i-k);l++){Add(f[i+1][j+1][k+l],1ll*val*C(i-k,l)%mod*C(cnt[j+1],l)%mod*fact[l]%mod);  //耐心值 >j+1if(k+l<pre[j+1]) Add(f[i+1][j+1][k+l+1],1ll*val*(pre[j+1]-k-l)%mod*C(i-k,l)%mod*C(cnt[j+1],l)%mod*fact[l]%mod);  //耐心值 <=j+1 }					}}}}int ans=0;for(int j=0;j<=n-m;j++) Add(ans,1ll*f[n][j][pre[j]]*fact[n-pre[j]]%mod);printf("%d\n",ans);cerr << fixed << setprecision(3) << (double)(clock()-beg) << endl;return 0;
}
后记
考场上玩了一整场原神,什么都没写出来。不知道场上咋想的,认为 \(dfn_x\le dfn_y\le dfn_x+siz_x-1\) 是二维偏序,结果就变成四维偏序了,然后都写一半了认为自己做法是错的把代码全删了。成功被机房所有同学偏序。
希望是给 NOIP 攒 RP 吧。