AGC043
B 123 Triangle
题意
给定长为 \(n\) 的序列 \(a\)。有递推关系 \(f_{k,x}=|f_{k-1,x}-f_{k-1,x+1}|(k>1,x\le n-k+1),f_{1,x}=a_x\)。求 \(f_{n,1}\)。
数据范围:\(n\le 10^6,1\le a_i\le 3\)
view solution
solution
先把 \(n=1\) 判掉,然后 \(a_i\in[0,2]\)。注意到 \(1\) 的特殊性:如果原序列存在 \(1\),那么最后答案一定是 \(0,1\) 中的一个。
对于 \(f_i\),如果存在一个 \(1\) 其左边/右边不是 \(1\),那么在 \(f_{i+1}\) 中一定存在至少一个 \(1\)。否则所有数都是 \(1\),\(f_{i+1}\) 中都是 \(0\)。
所以如果有 \(1\) 的话,只需要判断答案的奇偶性。注意到 \(|a-b|\bmod 2=a+b \bmod 2\),\(a_i\) 对 \(f_{n,0}\) 的贡献是 \(\binom{n-1}{i-1}\)。所以答案是
使用 Lucas 定理即可。
对于没有 \(1\) 的情况,把 \(2\) 视为 \(1\) 再跑即可。
view code
#include <bits/stdc++.h>
using namespace std;
const int N=1e6+5;
inline int binom(int n,int m){return (n&m)==m?1:0;}
int n,a[N];
char s[N];
int main(){
scanf("%d",&n);
scanf("%s",s+1);
for(int i=1;i<=n;++i)a[i]=s[i]-'0';
if(n==1){
printf("%d\n",a[1]);
return 0;
}
for(int i=1;i<n;++i)a[i]=abs(a[i]-a[i+1]);
--n;
bool flag=0;
for(int i=1;i<=n;++i){
if(a[i]==1)flag=1;
}
int key=flag?1:2;
int sum=0;
for(int i=1;i<=n;++i)
if(a[i]==key)sum^=binom(n-1,i-1);
printf("%d\n",sum?key:0);
return 0;
}
C Giant Graph
题意
给定三个简单无向图 \(G_1,G_2,G_3\),点数均为 \(n\)。
另根据这三张图构造一个有 \(n^3\) 个点的图 \(G\),图 \(G\) 上
- \(\forall (u,v)\in G_1,a,b\in[1,n]\),连边 \((u,a,b),(v,a,b)\)。
- \(\forall (u,v)\in G_2,a,b\in[1,n]\),连边 \((a,u,b),(a,b,v)\)。
- \(\forall (u,v)\in G_3,a,b\in[1,n]\),连边 \((a,b,u),(a,b,v)\)。
对于 \(G\) 中的任意一个点 \((x,y,z)\),定义其点权为 \(10^{18(x+y+z)}\)。
求 \(G\) 的最大权独立集的大小模 \(998244353\) 的值。
数据范围:\(n,m\le 10^5\)
view solution
solution
把原问题转博弈题。
首先因为一个点的权值很大,所以我们肯定优先选 \(x+y+z\) 大的,这样与 \((x,y,z)\) 有边相连的点肯定不能选。
我们把要选的点视为必败态,这样所有能一步走到必败态的点就是必胜态。答案是所有必胜态的点的权值之和。
把每张图边定向,从小的连向大的,这样变成一个博弈的 DAG,并求出所有点的 SG 函数。如果 \((x,y,z)\) 满足 \(sg_1(x)\oplus sg_2(y)\oplus sg_3(z)=0\),那么点 \((x,y,z)\) 是必败态,即我们需要选的点。注意到 \(sg\) 值是 \(O(\sqrt{n})\) 级别的,预处理每张图中,\(sg(a)=i\) 的所有 \(a\) 的 \(10^{18a}\) 之和,然后枚举 \(sg_1(x)\) 和 \(sg_2(y)\) 即可。复杂度 \(O(n+m)\)。
view code
#include <bits/stdc++.h>
using namespace std;
inline int read(){
int s=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9')s=(s<<3)+(s<<1)+ch-'0',ch=getchar();
return s*f;
}
const int N=1e5+5,mod=998244353;
inline int quick_pow(int a,int b){
int ret=1;
for(;b;b>>=1,a=1ll*a*a%mod)if(b&1)ret=1ll*ret*a%mod;
return ret;
}
const int pw=quick_pow(10,18);
int n;
struct Graph{
vector<int> e[N];
int cnt[N],sg[N],m,mx;
bool vis[N];
void dfs(int u){
if(sg[u]!=-1)return;
for(int v:e[u])
dfs(v);
for(int v:e[u])
vis[sg[v]]=1;
for(int i=0;;++i)if(!vis[i]){
sg[u]=i;
break;
}
for(int v:e[u])
vis[sg[v]]=0;
}
void work(){
m=read();
for(int i=1,u,v;i<=m;++i){
u=read();v=read();
if(u>v)swap(u,v);
e[u].push_back(v);
}
memset(sg+1,-1,n<<2);
for(int i=1;i<=n;++i){
dfs(i);cnt[sg[i]]=(cnt[sg[i]]+quick_pow(pw,i))%mod;
mx=max(mx,sg[i]);
}
}
}G1,G2,G3;
int main(){
n=read();
G1.work();
G2.work();
G3.work();
int ans=0;
for(int i=0;i<=G1.mx;++i)if(G1.cnt[i])
for(int j=0;j<=G2.mx;++j)if(G2.cnt[j])
if(G3.cnt[i^j])ans=(ans+1ll*G1.cnt[i]*G2.cnt[j]%mod*G3.cnt[i^j])%mod;
printf("%d\n",ans);
return 0;
}
D Merge Triplets
题意
有 \(n\) 个大小为 \(3\) 的序列,总共 \(3n\) 个元素,这 \(3n\) 个元素构成一个 \(1\sim 3n\) 的排列。
另有一个排列 \(P\),每次选出所有非空序列中的第一个元素中最小的一个,把这个元素放到 \(P\) 的末尾,并把它从其所在序列中删除。一直操作直到所有序列为空,即 \(P\) 变成一个 \(1\sim 3n\) 的排列。
对于所有生成 \(n\) 个序列的方式,求能生成多少个不同的排列 \(P\),答案取模。
数据范围:\(n\le 2000\)。
view solution
### solution对于一个大小为 \(3\) 的序列,它在 \(P\) 中的位置一定是下面三种情况之一:
- 一个长为为 \(3\) 的连续段
- 两个连续段,长度为 \(1+2/2+1\)
- 三个连续段,长度为 \(1,1,1\)
也就是,只要一个排列 \(P\) 满足以下限制,它一定能被构造出来:
能被前缀最大值分为若干段,每段的长度 \(\le 3\),且 \(2\) 的数量 \(\le 1\) 的数量。
DP,\(f_{i,j}\) 表示考虑完前 \(i\) 个数,\(1\) 的数量 \(-2\) 的数量 \(=j\) 的方案数。
复杂度 \(O(n^2)\)。
view code
#include <bits/stdc++.h>
using namespace std;
const int N=2005;
int C[N*3][3],mod;
inline int add(int a,int b){return (a+b>=mod)?a+b-mod:a+b;}
inline void init(int n){
C[0][0]=1;
for(int i=1;i<=n;++i){
C[i][0]=1;
for(int j=1;j<3;++j)C[i][j]=add(C[i-1][j-1],C[i-1][j]);
}
}
struct Val{
int f[6*N];
inline int& operator[](int x){return f[x+3*N];}
};
int n;
Val f[N*3];
int main(){
cin>>n>>mod;
init(n*3);
f[0][0]=1;
for(int i=1;i<=3*n;++i){
for(int j=1;j<=3&&j<=i;++j){
int v=C[i-1][j-1];
if(j==3)v=2ll*v%mod;
for(int k=-i+1;k<i;++k){
if(j==1)f[i][k+1]=(f[i][k+1]+1ll*v*f[i-j][k])%mod;
else if(j==2)f[i][k-1]=(f[i][k-1]+1ll*v*f[i-j][k])%mod;
else f[i][k]=(f[i][k]+1ll*v*f[i-j][k])%mod;
}
}
}
int ans=0;
for(int i=0;i<=3*n;++i)ans=add(ans,f[3*n][i]);
printf("%d\n",ans);
return 0;
}
E Topology
题意
平面上有 \(n\) 个点,分别位于 \((i+\frac{1}{2},\frac{1}{2})(i\in[0,n-1])\),以及一个封闭曲线 \(C\)。给定 \(2^n\) 个状态,每个状态 \(f_S\) 表示在考虑 \(S\) 集合内的所有点时,曲线 \(C\) 能否在不接触 \(S\) 集合内的点,移动到所有点的纵坐标都 \(<0\) 的位置。
请你根据 \(f\) 构造一个满足条件的 \(C\),或者判定无解。
数据范围:\(n\le 8\)
view solution
首先容易发现,\(f\) 具有传递性,即如果 \(f_{S}=1\),那么对于 \(T\subseteq S,f_{T}=1\);如果 \(f_{S}=0\),那么对于 \(S\subseteq T,f_{T}=0\)。如果违反了传递性显然无解。
我们现在找到所有的 \(S\),满足所有 \(S\) 的子集都能解出来,\(S\) 及 \(S\) 的所有超集都不能被解出来。如果我们的构造满足:对于 \(S\) 无法解出,而删掉任何一个点都能解出,那么我们把所有 \(S\) 的构造连到同一个点上把它们拼起来,就满足了题面的所有限制。
构造之前,先考虑 spj 怎么写:从封闭曲线的一个点出发走一圈,如果经过点 \(i\) 的上方,往序列里写下一个 \(u_i\);如果经过点 \(i\) 的下方,写下一个 \(t_i\)。如果出现了连续的 \(u_i,u_i\) 或者 \(t_i,t_i\),那么这两步可以删掉,不会影响 \(i\) 是否被 \(C\) 包住,也就不会影响 \(C\) 是否能解出。如果一直把整个序列都删完了,那么不存在任何一个点被包住,即 \(C\) 可以解出,否则 \(C\) 无法解出。
会了判定之后递归构造方案(方案要满足:对于 \(S\) 无法解出,而删掉任何一个点都能解出):
- 如果当前集合内只有 \(1\) 个点,把这个点包一圈即可
- 先把最靠左的 \(i\) 去掉,设 \(T\) 表示 \(S'=S\setminus \{i\}\) 的方案,那么我们构造 \(u_iTu_it_iT't_i\),\(T'\) 表示把 \(T\) 中的方案倒序之后的方案。容易发现,如果任何一个点被删掉(删掉它的所有 \(u_i,t_i\)),那么整个序列就能被删完;否则整个序列不能被删掉任何一个元素。
view code
#include <bits/stdc++.h>
using namespace std;
const int N=10;
#define pr pair<int,int>
#define mp make_pair
#define fi first
#define se second
#define pb push_back
#define ins(x,y) ret.pb(mp(x,y))
inline vector<pr> solve(int s,int pre){
int x=__builtin_ctz(s);
vector<pr> ret;
for(int i=pre+1;i<=x;++i)
ins(i,0);
if(__builtin_popcount(s)==1){
ins(x+1,0);ins(x+1,1);
ins(x,1);ins(x,0);
for(int i=x;i>pre;--i)
ins(i-1,0);
return ret;
}
ins(x+1,0);
vector<pr> T=solve(s^(1<<x),x+1);
for(pr p:T)ret.pb(p);
ins(x,0);
ins(x,1);
ins(x+1,1);
ins(x+1,0);
T.pop_back();
reverse(T.begin(),T.end());
for(pr p:T)ret.pb(p);
ins(x+1,0);
ins(x+1,1);
ins(x,1);
ins(x,0);
for(int i=x-1;i>=pre;--i)ins(i,0);
return ret;
}
int n;
char s[1<<N];
int a[N],f[1<<N];
vector<pr> ans;
int main(){
scanf("%d",&n);
scanf("%s",s);
int tot=(1<<n);
for(int i=0;i<tot;++i){
f[i]=s[i]-'0';
for(int j=0;j<n;++j)
if(i&(1<<j)){
if(f[i]==1&&f[i^(1<<j)]==0){
puts("Impossible");
return 0;
}
}
}
ans.pb(mp(0,0));
for(int i=1;i<tot;++i){
if(!f[i]){
bool flag=1;
for(int j=0;j<n;++j)if(i&(1<<j)){
if(!f[i^(1<<j)])flag=0;
}
if(flag){
vector<pr> cur=solve(i,0);
for(pr p:cur)ans.pb(p);
}
}
}
puts("Possible");
printf("%d\n",(int)ans.size()-1);
for(pr p:ans)printf("%d %d\n",p.fi,p.se);
return 0;
}
F Jewelry Box
题意
有 \(N\) 个珠宝商店。
每个商店卖 \(K_i\) 种珠宝,第 \(i\) 个商店的第 \(j(1\le j\le K_i)\) 种珠宝拥有三个独立的属性 \((S,P,C)\) 依次表示重量,价格,数量。
现在有 \(Q\) 组询问,每次给定一个 \(A_i\),询问能否够构造 \(A_i\) 个“珠宝盒”,如果可以则输出最小的花费(即购买的珠宝的价格之和)否则输出 \(-1\)
一个“珠宝盒”是一个包含 \(N\) 个珠宝的盒子,且满足如下条件:
- 盒子内部的第 \(i\) 个珠宝从第 \(i\) 个珠宝商店处购买。
- 满足 \(M\) 条约束:
- 对于第 \(i\) 条约束:此盒子内第 \(V_i\) 珠宝的重量应当不超过第 \(U_i\) 个珠宝的重量 \(+ W_i\)
数据范围:
\(N,K_i\le 30,S_{i,j}\le 10^9,P_{i,j}\le 30,C_{i,j}\le 10^{12},M\le 50,Q\le 10^5,A_i\le 3\times 10^{13},W_i\le 10^9\)
view soluiton
参见 sxTQX 的博客 线性规划对偶问题
view code
#include <bits/stdc++.h>
using namespace std;
#define int long long
inline int read(){
int s=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9')s=(s<<3)+(s<<1)+ch-'0',ch=getchar();
return s*f;
}
namespace Flow{
const int M=1e5+5,N=1e5+5,inf=1e17;
struct Edge{int to,next,flow,cost;}e[M];
int dis[N],head[N],cur[N],tot,ecnt=1,h[N],p[N];
inline void adde(int u,int v,int flow,int cost){
e[++ecnt]=(Edge){v,head[u],flow,cost};head[u]=ecnt;
e[++ecnt]=(Edge){u,head[v],0,-cost};head[v]=ecnt;
}
bool inq[N];
bool SPFA(int s,int t){
memset(dis+1,0x3f,tot<<3);
dis[s]=0;
queue<int> q;
q.push(s);
while(!q.empty()){
int u=q.front();q.pop();
inq[u]=0;
cur[u]=head[u];
for(int i=head[u],v;i;i=e[i].next){
if(!e[i].flow)continue;
v=e[i].to;
if(dis[v]>dis[u]+e[i].cost){
dis[v]=dis[u]+e[i].cost;
if(!inq[v])q.push(v),inq[v]=1;
}
}
}
memcpy(p+1,dis+1,tot<<3);
return dis[t]<=inf;
}
bool dijkstra(int s,int t){
for(int i=1;i<=tot;++i)h[i]+=p[i];
priority_queue<pair<int,int> > q;
q.push(make_pair(0,s));
memset(dis+1,0x3f,tot<<3);
memset(inq+1,0,tot);
dis[s]=0;
while(!q.empty()){
int u=q.top().second;q.pop();
if(inq[u])continue;
inq[u]=1;
cur[u]=head[u];
for(int i=head[u],v;i;i=e[i].next){
if(!e[i].flow)continue;
v=e[i].to;
if(dis[v]>dis[u]+e[i].cost-h[v]+h[u]){
dis[v]=dis[u]+e[i].cost-h[v]+h[u];
q.push(make_pair(-dis[v],v));
}
}
}
memcpy(p+1,dis+1,tot<<3);
return dis[t]<=inf;
}
bool vis[N];
int dinic(int u,int t,int flow){
if(u==t)
return flow;
vis[u]=1;
int ret=0,f;
for(int&i=cur[u];i;i=e[i].next){
int v=e[i].to;
if(!e[i].flow||dis[v]!=dis[u]+e[i].cost-h[v]+h[u]||vis[v])continue;
f=dinic(v,t,min(flow,e[i].flow));
e[i].flow-=f;e[i^1].flow+=f;
ret+=f;flow-=f;
}
vis[u]=0;
if(f)dis[u]=inf;
return ret;
}
}
const int N=55,inf=1e17;
int s[N][N],p[N][N],c[N][N],s1[N],p1[N],c1[N],k[N],n,id[N][N],tot;
int cnt,flow[N*N],cost[N*N],F[N*N],m,per[N];
inline bool cmp(int x,int y){
return s1[x]<s1[y];
}
signed main(){
n=read();
int S=++tot,T=++tot;
for(int i=1;i<=n;++i){
k[i]=read();
for(int j=1;j<=k[i];++j){
s1[j]=read();
p1[j]=read();
c1[j]=read();
if(j>1)id[i][j]=++tot;
else id[i][j]=S;
per[j]=j;
}
sort(per+1,per+1+k[i],cmp);
for(int j=1;j<=k[i];++j){
s[i][j]=s1[per[j]];
p[i][j]=p1[per[j]];
c[i][j]=c1[per[j]];
}
id[i][k[i]+1]=++tot;
for(int j=2;j<=k[i]+1;++j){
Flow::adde(id[i][j-1],id[i][j],p[i][j-1],0);
Flow::adde(id[i][j-1],id[i][j],inf,c[i][j-1]);
Flow::adde(id[i][j],id[i][j-1],inf,0);
}
Flow::adde(id[i][k[i]+1],T,inf,0);
}
Flow::tot=tot;
m=read();
while(m--){
int u,v,w;
u=read();v=read();w=read();
int flag=1;
for(int i=1;i<=k[v];++i){
while(flag<=k[u]&&s[u][flag]+w<s[v][i])++flag;
Flow::adde(id[v][i],id[u][flag],inf,0);
}
}
int mf=0,mc=0;
for(Flow::SPFA(S,T);Flow::dijkstra(S,T);){
int f=Flow::dinic(S,T,inf);
flow[++cnt]=mf;
cost[cnt]=mc;
int d=Flow::dis[T]+Flow::h[T]-Flow::h[S];
F[cnt]=d;
if((__int128)f*d>=inf)break;
mf+=f;
mc+=f*d;
}
int q=read();
while(q--){
int a=read();
int i=lower_bound(F+1,F+cnt+1,a)-F;
if(i>cnt)
puts("-1");
else printf("%lld\n",flow[i]*a-cost[i]);
}
return 0;
}