Atcoder247
ABC 247
E.Max Min
Problem
给定\(N\)个数和\(X\)和\(Y\),问有多少个区间满足这个区间的最大数是\(X\),最小数是\(Y\)
Sol
考虑容斥,\(x\)和\(y\)至少满足一个的区间数\(-\)不满足\(x\)的区间数\(-\)不满足\(y\)的区间数\(+\) \(x\)和\(y\)都不满足的区间数
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int N=1505;
string g[N];
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n,x,y;
cin>>n>>x>>y;
vector<int>a(n+1);
for(int i=1;i<=n;i++) cin>>a[i];
auto f=[&](int x,int y)->ll{
ll ans=0;
for(int i=1,j=1;i<=n;i++)
{
if(a[i]>x||a[i]<y)
{
j=i+1;
}
ans+=i-j+1;
}
return ans;
};
//x和y至少满足一个的区间数-不满足x的区间数-不满足y的区间数+x和y都不满足的区间数
cout<<f(x,y)-f(x-1,y)-f(x,y+1)+f(x-1,y+1)<<'\n';
return 0;
}
F.Cards
Problem
有\(N\)张卡片,每张卡片\(i\)上有数字\(P_i\)和\(Q_i\),并且\(P\)和\(Q\)都是\(1-N\)的一个排列。现在问有多少种选择卡牌的方法,使得选出的卡牌包含所有\(1-N\)的数字,答案对\(998244353\)取模。
Sol
对于样例
2 3 5 4 1
4 2 1 3 5
发现\(2,4,3\)是在一个环里的,\(1,5\)是在一个环里中。对于一个环上的三条边\(a\rightarrow b\rightarrow c\rightarrow d\),选\(a\rightarrow b\)和\(c\rightarrow d\)就可以不用选\(b\rightarrow c\)这条边。那么对于一个环,就是说环上不能有连续的两条边不选,那么考虑在环上\(dp\),设\(dp[i][s][t],(s,t=0/1)\)表示我们可以任意选一条边作为起始边,当来到第\(i\)条边,起始边的选的状态为\(s\),当前边选的状态为\(t\),假设环的大小为\(k\),那么\(f[k]=dp[k][1][0]+dp[k][1][1]+dp[k][0][1]\),若起始边选了,第\(k\)条边为终止边可选可不选,若起始边没有选,那么终止边必须选。
其实,这样的方案数是一个有规律的序列:\(f(1)=1,f(2)=3,f(3)=4,f(N)=f(N-1)+f(N-3)\)
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int mod=998244353;
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n;
cin>>n;
vector<int>f(n+1);
for(int i=1;i<=n;i++) f[i]=i;
vector<int>p(n+1),q(n+1);
auto find=[&](auto self,int x)->int{
return f[x]==x?x:f[x]=self(self,f[x]);
};
for(int i=1;i<=n;i++) cin>>p[i];
for(int i=1;i<=n;i++) cin>>q[i];
for(int i=1;i<=n;i++)
f[find(find,p[i])]=find(find,q[i]);
vector<int>sz(n+1);
for(int i=1;i<=n;i++) sz[find(find,p[i])]++;
vector<vector<vector<ll>>>dp(n+1,vector<vector<ll>>(2,vector<ll>(2)));
dp[1][0][0]=dp[1][1][1]=1;
vector<ll>ans(n+1);//记录大小为i的环中不能有同时两条连续边不选的方案数
ans[1]=1;
for(int i=2;i<=n;i++)
{
for(int s=0;s<2;s++)
{
dp[i][s][0]=dp[i-1][s][1];
dp[i][s][1]=(dp[i-1][s][0]+dp[i-1][s][1])%mod;
}
ans[i]=(dp[i][1][1]+dp[i][0][1]+dp[i][1][0])%mod;
}
ll res=1;
for(int i=1;i<=n;i++)if(f[i]==i)res=res*ans[sz[i]]%mod;
cout<<res<<'\n';
return 0;
}
G.Dream Team
Problem
有\(N\)工程师,第\(i\)个工程师来自大学\(A_i\),擅长科目\(B_i\),有\(C_i\)的价值。现在可以从这\(N\)个人中选出若干个来组成一个团队,要求团队里的任意两个人不能来自同一个大学并且不能擅长同一个科目。令\(k\)为最大的可以选的人数,对于\(i=1,2,...k\),输出由\(i\)个人组成的团队的最大价值。
Sol
题意转化就是一个\(A_i\)只能匹配一个\(B_j\),并且只能选一个\(A_i\),一个\(B_j\),于是可以这样建边:
- \(S\)向每个\(A_i\)连流量为\(1\),费用为\(0\)的边
- 每个\(B_iS\)向\(T\)连流量为\(1\),费用为\(0\)的边
- 对于每个工程师,\(A_i\)向\(B_i\)连流量为\(1\),费用为\(-C_i\)的边(加符号方便求最小费用)
由于每次增广\(1\)的流量,把每次增广完的费用记录就是团队人数为\(i\)的答案。
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N=5005,M=50005*2,inf=1e8;
int n,m,S,T,cnt;
int head[N],d[N],st[N],pre[N],incf[N];
struct edges
{
int v,nxt,f,c;
}e[M];
void add(int u,int v,int f,int c)
{
e[cnt].v=v,e[cnt].nxt=head[u],e[cnt].f=f,e[cnt].c=c,head[u]=cnt++;
e[cnt].v=u,e[cnt].nxt=head[v],e[cnt].f=0,e[cnt].c=-c,head[v]=cnt++;
}
bool spfa()
{
memset(d,0x7f,sizeof d);
memset(incf,0,sizeof incf);
queue<int>q;
q.push(S);
d[S]=0,incf[S]=inf;
while(q.size())
{
int u=q.front();
q.pop();
st[u]=0;
for(int i=head[u];~i;i=e[i].nxt)
{
int v=e[i].v,f=e[i].f,c=e[i].c;
if(f&&d[v]>d[u]+c)
{
d[v]=d[u]+c;
pre[v]=i;
incf[v]=min(incf[u],f);
if(!st[v])
{
st[v]=1;
q.push(v);
}
}
}
}
return incf[T]>0;
}
void EK(int &flow,int &cost)
{
flow=cost=0;
vector<int>ans;
while(spfa())
{
int t=incf[T];
flow+=t,cost+=t*d[T];
ans.push_back(cost);
for(int i=T;i!=S;i=e[pre[i]^1].v)
{
e[pre[i]].f-=t,e[pre[i]^1].f+=t;
}
}
cout<<flow<<'\n';
for(auto x:ans) cout<<-x<<'\n';
}
signed main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
memset(head,-1,sizeof head);
int n;
cin>>n;
S=0,T=301;
for(int i=1;i<=150;i++) add(S,i,1,0);
for(int i=151;i<=3000;i++) add(i,T,1,0);
for(int i=1;i<=n;i++)
{
int a,b,c;
cin>>a>>b>>c;
add(a,150+b,1,-c);
}
int flow,cost;
EK(flow,cost);
return 0;
}
Ex.Rearranging Problem
Problem
有\(N\)个人,第\(i\)个人穿着颜色为\(c_i\)的衣服。现在你可以进行恰好\(k\)次操作,每次操作可以交换\(2\)个人的位置,问在进行\(k\)次交换后,序列的颜色和没有操作之前的序列颜色一样的前提下,这样的序列多少个,答案对\(998244353\)取模。
Sol
首先假设一个长度为\(n\)的排列形成了\(k\)个环,那么两两交换使得排列变成\(1-n\)最多需要\(n-k\)次,因为一个环上的数最多进行\(sz_k-1\)次循环移动会使每个数到达需要的位置上。接着这个题目,对于一种颜色\(c\),假设它有\(a\)个,那么把它分成\(i=1,2,...a\)个有向环的方案数记为\(f(i)\),对于其他颜色同理,最后答案就是\(\prod_{i=1}^mf_m(j_i)\),满足\(\sum_{i=m}^nj_i\equiv k(mod\ 2)\),那么\(f(i)\)怎么算呢。其实\(f(i)\)就是第一类斯特林数
斯特林数
第一类斯特林数(斯特林轮换数)\(n\brack k\),也可记做\(s(n,k)\) ,表示将\(n\)个两两不同的元素,划分为\(k\)个互不区分的非空轮换的方案数。一个轮换就是一个首尾相接的环形排列。我们可以写出一个轮换\([A,B,C,D]\) ,并且我们认为\([A,B,C,D]=[B,C,D,A]=[C,D,A,B]=[D,A,B,C]\) ,即,两个可以通过旋转而互相得到的轮换是等价的。注意,我们不认为两个可以通过翻转而相互得到的轮换等价,即\([A,B,C,D]\neq [D,C,B,A]\) 。
递推式:\({n\brack k}={n-1\brack k-1}+(n-1){n-1 \brack k}\)
生成函数(同行):\(F_n(x)=\prod_{i=0}^{n-1}(x+i)=\frac{(x+n-1)!}{(x-1)!}=\prod_{i=0}^n{n\brack i}x^i\)
生成函数(同列):\(F(x)=\sum_{i=1}^n\frac{(i-1)!x^i}{i!}=\sum_{i=1}^n\frac{x^i}{i}=\sum_{i=1}^n{i \brack k}x^i\)
#include <bits/stdc++.h>
#define ll long long
#define pb push_back
using namespace std;
const int N = 550000;
const int p = 998244353, gg = 3, ig = 332738118, img = 86583718;//998244353 1004535809
//924844033=>gg=5
const int M=18e5;
const int mod=998244353;
//const ll mod = 31525197391593473, G = 3;
ll qpow(ll a, int b)
{
ll res = 1;
while(b) {
if(b & 1) res = 1ll * res * a % mod;
a = 1ll * a * a % mod;
b >>= 1;
}
return res;
}
namespace Poly{
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
Poly::init(19);
int n,k;
cin>>n>>k;
vector<int>c(n+1);
for(int i=0;i<n;i++)
{
int x;
cin>>x;
c[x]++;
}
vector<Poly::poly>F;
for(int i=1;i<=n;i++)
if(c[i])
{
if(c[i]==1) F.push_back(Poly::poly({1}));
else for(int j=1;j<c[i];j++) F.push_back(Poly::poly({1,j}));
}
int n_=F.size();
auto CDQ=[&](auto self,int l,int r)->Poly::poly{
if(l==r) return F[l];
int mid=l+r>>1;
return Poly::poly_mul(self(self,l,mid),self(self,mid+1,r));
};
auto a=CDQ(CDQ,0,n_-1);
ll ans=0;
for(int i=0;i<a.size();i++)
if(i%2==k%2&&i<=k) ans=(ans+a[i])%mod;
cout<<ans<<'\n';
return 0;
}