2022-2023 ICPC, Asia Yokohama Regional Contest 2022
文章目录
- A. Hasty Santa Claus
- B. Interactive Number Guessing
- C. Secure the Top Secret
- D. Move One Coin
- E. Incredibly Cute Penguin Chicks
- F. Make a Loop
- G. Remodeling the Dungeon
- H. Cake Decoration
- I. Quiz Contest
- J. Traveling Salesperson in an Island
- K. New Year Festival
A. Hasty Santa Claus
思路:贪心。将区间优先按照 a i a_i ai其次 b i b_i bi大小进行排序,然后从第 1 1 1天枚举至第 31 31 31天,每次取出前 k k k个house进行拜访。复杂度 O ( n log 2 n ) O(n\log_2n) O(nlog2n)。
#include<bits/stdc++.h>
#define fi first
#define se second
#define lson (k<<1)
#define rson (k<<1)+1
#define mid ((l+r)/2)
#define sz(x) int(x.size())
#define pii pair<int,int>
#define bit bitset<100000>
using namespace std;
const int MAX=3e5+10;
const int MOD=998244353; //G=3
const int INF=INT_MAX;
const double PI=acos(-1.0);
typedef int64_t ll;
struct lenka{int l,r,idx;};
bool cmp(lenka a,lenka b){return a.l==b.l?a.r<b.r:a.l<b.l;}
int ans[1100];
int solve()
{
int n,k;
cin>>n>>k;
vector<lenka>a;
a.resize(n);
for(int i=0;i<n;i++)scanf("%d%d",&a[i].l,&a[i].r),a[i].idx=i;
sort(a.begin(),a.end(),cmp);
for(int i=1;i<=31;i++)
{
vector<int>v;
int m=k;
for(int j=0;j<sz(a)&&m;j++)
{
if(i<a[j].l)continue;
v.push_back(j);
ans[a[j].idx]=i;
m--;
}
reverse(v.begin(),v.end());
for(int j:v)a.erase(a.begin()+j);
for(auto &j:a)j.l=max(j.l,i+1);
sort(a.begin(),a.end(),cmp);
}
for(int i=0;i<n;i++)printf("%d\n",ans[i]);
return 1;
}
int main()
{
int T=1;
//cin>>T;
while(T--)solve();
return 0;
}
B. Interactive Number Guessing
思路:考虑一位一位确定,二分区间 [ 0 , 9 ] [0,9] [0,9],当 x i + m i d ≥ 10 x_i+mid\ge10 xi+mid≥10时,会产生进位,数位和就会变少,否则会变多。
#include<bits/stdc++.h>
#define fi first
#define se second
#define lson (k<<1)
#define rson (k<<1)+1
#define mid ((l+r)/2)
#define sz(x) int(x.size())
#define pii pair<int,int>
#define bit bitset<100000>
using namespace std;
const int MAX=3e5+10;
const int MOD=998244353; //G=3
const int INF=INT_MAX;
const double PI=acos(-1.0);
typedef int64_t ll;
ll ask(ll x)
{
cout<<"query "<<x<<endl;
cin>>x;
return x;
}
int solve()
{
ll ans=0,pre=1;
ll s=ask(0);
for(ll i=0;i<18;i++)
{
ll l=0,r=9;
while(r>=l)
{
ll ret=ask(pre*mid)-s;
if(ret<0)r=mid-1;
if(ret>0)l=mid+1;
if(ret==0)
{
if(mid==9)ans+=pre;
break;
}
}
ans+=pre*(9-mid);
pre*=10;
}
cout<<"answer "<<ans<<endl;
return 1;
}
int main()
{
int T=1;
//cin>>T;
while(T--)solve();
return 0;
}
C. Secure the Top Secret
题解
D. Move One Coin
思路:考虑将硬币的坐标排序后,用坐标的差分数组是否相同来判断是否匹配,因为至多只需移动一枚硬币即可匹配,所以2个图的差分数组不同项最多为2个,且一张图一个,当碰到不同项时,直接枚举删除硬币后判断是否匹配即可。复杂度 O ( H ∗ W ) O(H*W) O(H∗W)。
#include<bits/stdc++.h>
#define fi first
#define se second
#define lson (k<<1)
#define rson (k<<1)+1
#define mid ((l+r)/2)
#define sz(x) int(x.size())
#define pii pair<int,int>
#define bit bitset<100000>
using namespace std;
const int MAX=1e6+10;
const int MOD=998244353; //G=3
const int INF=INT_MAX;
const double PI=acos(-1.0);
typedef int64_t ll;
struct lenka
{
int n,m;
char s[510][510];
void read()
{
scanf("%d%d",&n,&m);
for(int i=0;i<n;i++)scanf("%s",s[i]);
}
void rota()
{
char tmp[510][510];
for(int i=0;i<m;i++)
for(int j=0;j<n;j++)tmp[i][j]=s[j][i];
swap(n,m);
for(int i=0;i<n;i++)
for(int j=0;j<m;j++)s[i][j]=tmp[i][m-1-j];
}
vector<pii> seri()
{
vector<pii>v;
for(int i=0;i<n;i++)
for(int j=0;j<m;j++)
{
if(s[i][j]!='o')continue;
v.push_back({i,j});
}
return v;
}
};
pii operator-(pii a,pii b){return {a.fi-b.fi,a.se-b.se};}
pii operator+(pii a,pii b){return {a.fi+b.fi,a.se+b.se};}
int print(vector<pii> a){for(pii &kv:a)printf("%d %d\n",kv.se,kv.fi);return 1;}
int check(vector<pii> &a,vector<pii> &b)
{
set<pii>s;
for(int i=1;i<sz(a);i++)s.insert(a[i]-a[0]);
for(int i=1;i<sz(b);i++)
{
if(s.count(b[i]-b[0]))s.erase(b[i]-b[0]);
else s.insert(b[i]-b[0]);
}
return s.size();
}
pii get(vector<pii> &a,vector<pii> &b)
{
set<pii>s;
for(int i=1;i<sz(a);i++)s.insert(a[i]-a[0]);
for(int i=1;i<sz(b);i++)s.erase(b[i]-b[0]);
return *s.begin();
}
int f(vector<pii> a,vector<pii> b)
{
if(check(a,b)==0)return print({a[0],a[0]});
if(sz(a)==2)return print({a[1],b[1]-b[0]+a[0]});
if(check(a,b)>2)
{
auto ta=a,tb=b;
ta.erase(ta.begin());
if(check(ta,b)==1)return print({a[0],ta[0]+get(b,ta)});
tb.erase(tb.begin());
if(check(tb,a)==1)return print({a[0]+get(a,tb),a[0]-(b[1]-b[0])});
if(check(ta,tb)==0)return print({a[0],a[1]-(b[1]-b[0])});
return 0;
}
assert(check(a,b)==2);
print({a[0]+get(a,b),a[0]+get(b,a)});
return 1;
}
int solve()
{
lenka a,b;
a.read();
b.read();
for(int i=0;i<6;i++)
{
if(f(a.seri(),b.seri()))return 0;
b.rota();
}
return 0;
}
int main()
{
int T=1;
//cin>>T;
while(T--)solve();
return 0;
}
E. Incredibly Cute Penguin Chicks
思路:用 C [ i ] , I [ i ] , P [ i ] C[i],I[i],P[i] C[i],I[i],P[i]分别代表各字母数量的前缀和,用 d [ i ] d[i] d[i]表示 [ 0 , i ] [0,i] [0,i]的方案数,则 d i = ∑ d j d_i=\sum d_j di=∑dj,当且仅当以下任一条件满足
- C i − C j = I i − I j , P i − P j > C i − C j C_i-C_j=I_i-I_j,P_i-P_j>C_i-C_j Ci−Cj=Ii−Ij,Pi−Pj>Ci−Cj
- C i − C j = P i − P j , I i − I j > C i − C j C_i-C_j=P_i-P_j,I_i-I_j>C_i-C_j Ci−Cj=Pi−Pj,Ii−Ij>Ci−Cj
- I i − I j = P i − P j , C i − C j > I i − I j I_i-I_j=P_i-P_j,C_i-C_j>I_i-I_j Ii−Ij=Pi−Pj,Ci−Cj>Ii−Ij
转移复杂度为 O ( n 2 ) O(n^2) O(n2),考虑优化,将上面条件式子转化一下得
- C i − I i = C j − I j , P i − C i > P j − C j C_i-I_i=C_j-I_j,P_i-C_i>P_j-C_j Ci−Ii=Cj−Ij,Pi−Ci>Pj−Cj
- C i − P i = C j − P j , I i − C i > I j − C j C_i-P_i=C_j-P_j,I_i-C_i>I_j-C_j Ci−Pi=Cj−Pj,Ii−Ci>Ij−Cj
- I i − P i = I j − P j , C i − I i > C j − I j I_i-P_i=I_j-P_j,C_i-I_i>C_j-I_j Ii−Pi=Ij−Pj,Ci−Ii>Cj−Ij
如此我们只需分别记住在
{
C
i
−
I
i
,
P
i
−
C
i
}
,
{
C
i
−
P
i
,
I
i
−
C
i
}
,
{
I
i
−
P
i
,
C
i
−
I
i
}
\{C_i-I_i,P_i-C_i\},\{C_i-P_i,I_i-C_i\},\{I_i-P_i,C_i-I_i\}
{Ci−Ii,Pi−Ci},{Ci−Pi,Ii−Ci},{Ii−Pi,Ci−Ii}下的
d
i
d_i
di的值,再根据转化后的式子去dp转移。
因为式子中需要判断大小,我们可以预处理出所有可能的值并离散化,dp转移时二分查找求区间和,并记录下当前dp值,用树状数组来求区间和以及单点修改。复杂度
O
(
n
log
2
n
)
O(n\log_2n)
O(nlog2n)。
#include<bits/stdc++.h>
#define fi first
#define se second
#define lson (k<<1)
#define rson (k<<1)+1
#define mid ((l+r)/2)
#define sz(x) int(x.size())
#define pii pair<ll,ll>
#define bit bitset<100000>
using namespace std;
const int MAX=1e6+10;
const int MOD=998244353; //G=3
const int INF=INT_MAX;
const double PI=acos(-1.0);
typedef int64_t ll;
struct lenka
{
map<int,vector<int>> ma;
map<int,map<int,int>> idx;
void push(int a,int b,int c){ma[a-b].push_back(c-a);}
void uniq()
{
for(auto &kv:ma)
{
auto &v=kv.se;
v.push_back(-MAX);
v.push_back(0);
sort(v.begin(),v.end());
v.erase(unique(v.begin(),v.end()),v.end());
for(int i=0;i<sz(v);i++)idx[kv.fi][v[i]]=i;
fill(v.begin(),v.end(),0);
}
}
void add(int a,int b,int c,int y)
{
auto &v=ma[a-b];
int x=idx[a-b][c-a];
while(x+1<=sz(v))
{
v[x]+=y;
v[x]%=MOD;
x+=x&(-x);
}
}
int ask(int a,int b,int c)
{
auto &v=ma[a-b];
int x=idx[a-b][c-a]-1;
int sum=0;
while(x>0)(sum+=v[x])%=MOD,x-=x&(-x);
return sum;
}
}v[3];
char s[MAX];
int solve()
{
scanf("%s",s);
int n=strlen(s);
int sc=0,si=0,sp=0;
for(int i=0;i<n;i++)
{
sc+=s[i]=='C';
si+=s[i]=='I';
sp+=s[i]=='P';
v[0].push(si,sp,sc);
v[1].push(sc,sp,si);
v[2].push(sc,si,sp);
}
for(int i=0;i<3;i++)
{
v[i].uniq();
v[i].add(0,0,0,1);
}
sc=si=sp=0;
for(int i=0;i<n;i++)
{
sc+=s[i]=='C';
si+=s[i]=='I';
sp+=s[i]=='P';
int ac=v[0].ask(si,sp,sc);
int ai=v[1].ask(sc,sp,si);
int ap=v[2].ask(sc,si,sp);
int sum=((ac+ai)%MOD+ap)%MOD;
if(i+1==n)return printf("%d\n",sum);
v[0].add(si,sp,sc,sum);
v[1].add(sc,sp,si,sum);
v[2].add(sc,si,sp,sum);
}
return 0;
}
int main()
{
int T=1;
//cin>>T;
while(T--)solve();
return 0;
}
F. Make a Loop
思路:因为最后所有弧要连接闭合,那么最终这些圆弧的向量和一定为0。我们可以依次将所有弧区分为4个部分,如下图:
假设图中起点为
S
S
S,方向为逆时针方向。绿色部分圆弧向量为
(
−
r
i
,
−
r
i
)
(-r_i,-r_i)
(−ri,−ri),半径总和为
S
1
S_1
S1。黄色部分向量为
(
r
i
,
−
r
i
)
(r_i,-r_i)
(ri,−ri),半径总和为
S
2
S_2
S2。蓝色部分向量为
(
r
i
,
r
i
)
(r_i,r_i)
(ri,ri),半径总和为
S
3
S_3
S3。红色部分向量为
(
−
r
i
,
r
i
)
(-r_i,r_i)
(−ri,ri),半径总和为
S
4
S_4
S4。由最终向量和为0得
S
1
+
S
4
=
S
2
+
S
3
S
1
+
S
2
=
S
4
+
S
3
S
1
+
S
2
+
S
4
+
S
3
=
∑
1
≤
i
≤
n
r
i
S_1+S_4=S_2+S_3\\ S_1+S_2=S_4+S_3\\ S_1+S_2+S_4+S_3=\sum_{1\le i\le n} r_i
S1+S4=S2+S3S1+S2=S4+S3S1+S2+S4+S3=1≤i≤n∑ri由上面的式子可得
S
1
+
S
4
=
S
2
+
S
3
=
S
1
+
S
2
=
S
3
+
S
4
=
∑
r
i
2
S_1+S_4=S_2+S_3=S_1+S_2=S_3+S_4=\frac{\sum r_i}2
S1+S4=S2+S3=S1+S2=S3+S4=2∑ri,同时因为最终所有弧会链接闭合,角度和为360°,所以4个部分里圆弧个数的奇偶性一定相等,那么任意2个部分的圆弧个数和一定是偶数。
综上所述,如果存在至少4种方法,使得我们从所有圆弧中选出偶数个圆弧的半径和,为总半径和的一半时,那我们一定有办法将所有圆弧首尾相连形成闭环。
采用dp,
d
[
0
/
1
]
[
s
]
d[0/1][s]
d[0/1][s]表示选择偶数
/
/
/奇数个弧且半径和为
s
s
s的方案数,当
d
[
0
]
[
∑
r
i
2
]
≥
4
d[0][\frac{\sum r_i}2]\ge4
d[0][2∑ri]≥4时为Yes。复杂度
O
(
n
∑
r
i
)
O(n\sum r_i)
O(n∑ri)。
#include<bits/stdc++.h>
#define fi first
#define se second
#define lson (k<<1)
#define rson (k<<1)+1
#define mid ((l+r)/2)
#define sz(x) int(x.size())
#define pii pair<ll,ll>
using namespace std;
const int MAX=500010;
const int MOD=1e9+7;
const double PI=acos(-1.0);
typedef long long ll;
int d[2][MAX];
int solve()
{
int n;
cin>>n;
vector<int>a;
a.resize(n);
for(int i=0;i<n;i++)scanf("%d",&a[i]);
int m=accumulate(a.begin(),a.end(),0);
if(m%2)return puts("No");
memset(d,0,sizeof d);
d[0][0]=1;
m/=2;
for(int i=0;i<n;i++)
for(int j=m;j>=a[i];j--)
{
d[0][j]=min(d[0][j]+d[1][j-a[i]],4);
d[1][j]=min(d[1][j]+d[0][j-a[i]],4);
}
return puts(d[0][m]==4?"Yes":"No");
}
int main()
{
int T=1;
// cin>>T;
while(T--)solve();
return 0;
}
G. Remodeling the Dungeon
思路:图为一棵树,入口和出口之间只有一条唯一路径,以入口为根节点建图,那我们只需要从出口开始枚举删除出口至入口路径上的边,删一条边会将图分割为底部和根2棵子树,因为是从底部向根枚举,所以底部每次都会新加入一些点,在删边的时候,我们只需要枚举这些新加入的点对答案的贡献即可。复杂度 O ( h ∗ w ) O(h*w) O(h∗w)。
#include<bits/stdc++.h>
#define fi first
#define se second
#define lson (k<<1)
#define rson (k<<1)+1
#define mid ((l+r)/2)
#define sz(x) int(x.size())
#define pii pair<int,int>
#define bit bitset<100000>
using namespace std;
const int MAX=3e5+10;
const int MOD=998244353; //G=3
const int INF=INT_MAX;
const double PI=acos(-1.0);
typedef int64_t ll;
vector<int>e[MAX];
vector<int>wall[MAX];
int fa[MAX],vis[MAX],d[MAX];
void dfs(int k,int f)
{
d[k]=d[f]+1;
fa[k]=f;
vis[k]=1;
for(int son:e[k])if(son!=f)dfs(son,k);
}
void get(int k,int from, vector<int> &v)
{
vis[k]=0;
v.push_back(k);
for(int son:e[k])
{
if(son==fa[k]||son==from)continue;
get(son,from,v);
}
}
int ans=0;
void cal(int k,int from,int dep)
{
if(k==0)return;
vector<int>v;
get(k,from,v);
for(int i:v)
for(int j:wall[i])
{
if(vis[j]==0)continue;
ans=max(ans,d[j]+d[i]-d[k]+dep-d[k]+1);
}
cal(fa[k],k,dep);
}
int solve()
{
int n,m;
cin>>n>>m;
vector<string>s;
s.resize(2*n+1);
for(string &i:s)cin>>i;
auto id=[m](int x,int y){return ((x-1)/2)*m+(y-1)/2+1;};
int h=2*n+1;
int w=2*m+1;
memset(wall,0,sizeof wall);
for(int i=1;i<h;i+=2)
for(int j=1;j<w;j+=2)
{
if(j+2<w)
{
int x=id(i,j),y=id(i,j+2);
if(s[i][j+1]=='.')
{
e[x].push_back(y);
e[y].push_back(x);
}
else
{
wall[x].push_back(y);
wall[y].push_back(x);
}
}
if(i+2<h)
{
int x=id(i,j),y=id(i+2,j);
if(s[i+1][j]=='.')
{
e[x].push_back(y);
e[y].push_back(x);
}
else
{
wall[x].push_back(y);
wall[y].push_back(x);
}
}
}
memset(d,0,sizeof d);
memset(fa,0,sizeof fa);
memset(vis,0,sizeof vis);
dfs(1,0);
ans=d[n*m];
cal(n*m,0,d[n*m]);
return printf("%d\n",ans);
}
int main()
{
int T=1;
//cin>>T;
while(T--)solve();
return 0;
}
H. Cake Decoration
思路:考虑构造四个数 a , b , c , d a,b,c,d a,b,c,d 并使 a < b < c < d a<b<c<d a<b<c<d。我们枚举 a , b a,b a,b,可以得到 c ∗ d c*d c∗d的值为 s = X a ∗ b s=\frac X{a*b} s=a∗bX,要满足 b < c < d b<c<d b<c<d 那么 c c c的值域即为 [ b + 1 , ⌊ s ⌋ ] [b+1,\lfloor\sqrt s\rfloor] [b+1,⌊s⌋]。然后考虑计算以下6情况的方案数
- L ≤ a + b < R L\le a+b\lt R L≤a+b<R
- L ≤ a + c < R L\le a+c\lt R L≤a+c<R
- L ≤ a + d < R L\le a+d\lt R L≤a+d<R
- L ≤ b + c < R L\le b+c\lt R L≤b+c<R
- L ≤ b + d < R L\le b+d\lt R L≤b+d<R
- L ≤ c + d < R L\le c+d\lt R L≤c+d<R
其中1-5种方案均包含
a
,
b
a,b
a,b,我们可以通过
L
,
R
,
a
,
b
L,R,a,b
L,R,a,b反推出方案数。
第6种方案只知道
c
c
c的值域,因为
c
<
d
c<d
c<d 且
c
+
d
c+d
c+d 的和满足单调性,可以用二分求出上下界得出方案数。枚举
a
,
b
a,b
a,b复杂度
O
(
X
)
O(\sqrt X)
O(X),二分复杂度
O
(
log
2
X
)
O(\log_2X)
O(log2X),总复杂度
O
(
X
log
2
X
)
O(\sqrt X\log_2X)
O(Xlog2X)。
#include<bits/stdc++.h>
#define fi first
#define se second
#define lson (k<<1)
#define rson (k<<1)+1
#define mid ((l+r)/2)
#define sz(x) int(x.size())
#define pii pair<ll,ll>
#define bit bitset<100000>
using namespace std;
const int MAX=1e7+10;
const int MOD=998244353; //G=3
const int INF=INT_MAX/2;
const double PI=acos(-1.0);
typedef int64_t ll;
void add(ll &x,ll y){x=(x+max(0ll,y%MOD))%MOD;}
ll cal(ll X,ll R)
{
R--;
ll ans=0;
for(ll a=1;a*(a+1)*(a+2)*(a+3)<=X;a++)
for(ll b=a+1;a*b*(b+1)*(b+2)<=X;b++)
{
ll s=X/a/b;
ll m=sqrt(s);
while(m*(m+1)<=s)m++;
while(m*(m+1)>s)m--;
if(m<=b)continue;
if(a+b<=R)add(ans,m-b);//a,b
add(ans,min(m,R-a)-b); //a,c
add(ans,min(m,R-b)-b); //b,c
if(a<R)
{
ll l=max(b+1,s/(R-a));
l+=(s/l+a>R);
add(ans,m-l+1); //a,d
}
if(b<R)
{
ll l=max(b+1,s/(R-b));
l+=(s/l+b>R);
add(ans,m-l+1); //b,d
}
ll l=b+1,r=m,down=m+1;
while(r>=l)
{
ll x=mid,y=s/mid;
if(x+y>R)l=mid+1;
else down=mid,r=mid-1;
}
add(ans,m-down+1);// c,d
}
return 4*ans%MOD;
}
int solve()
{
ll X,L,R;
cin>>X>>L>>R;
return printf("%lld\n",(cal(X,R)-cal(X,L)+MOD)%MOD);
}
int main()
{
int T=1;
//cin>>T;
while(T--)solve();
return 0;
}
I. Quiz Contest
思路:先按照正常思路解题,然后考虑优化。依次遍历第
i
i
i个competitor,假设该选手在第
k
k
k题获胜,那么第
i
i
i个选手获胜的方案数为
A
k
−
b
i
∗
(
k
−
1
)
!
∗
b
i
∗
C
a
i
b
i
∗
(
m
−
1
−
k
)
!
\red{A_{k-b_i}*(k-1)!}*b_i*C_{a_i}^{bi}* \green{(m-1-k)!}
Ak−bi∗(k−1)!∗bi∗Caibi∗(m−1−k)!其中红色部分式子代表
[
1
,
k
−
1
]
[1,k-1]
[1,k−1]题的排列数,中间黑色式子代表第
k
k
k题的排列数,绿色部分代表
[
k
+
1
,
m
]
[k+1,m]
[k+1,m]题目的排列数。
而
A
k
−
b
i
\red{A_{k-b_i}}
Ak−bi代表从除
a
i
a_i
ai之外的其余题目中选出
k
−
b
i
k-b_i
k−bi个题目,且其他选手均不会比
i
i
i提前获胜的方案数(其他选手不可以提前答对所有
b
b
b个题目)。那么
A
A
A就需要dp来求了,用
d
[
j
]
[
g
]
d[j][g]
d[j][g]表示从前
j
j
j个选手的题目中选出
g
g
g个题目的合法方案数,转移方程如下:
d
j
,
g
=
∑
p
=
0
b
j
−
1
d
j
−
1
,
g
−
p
∗
C
a
j
p
d_{j,g}=\sum_{p=0}^{b_j-1} d_{j-1,g-p}*C_{a_j}^p
dj,g=p=0∑bj−1dj−1,g−p∗Cajp知道了在第
k
k
k题获胜的方案数后,那么选手
i
i
i获胜的总方案数为
=
∑
k
=
b
i
A
k
−
b
i
∗
(
k
−
1
)
!
b
i
∗
C
a
i
b
i
∗
(
m
−
1
−
k
)
!
=
b
i
∗
C
a
i
b
i
∗
∑
k
=
b
i
A
k
−
b
i
∗
(
k
−
1
)
!
∗
(
m
−
1
−
k
)
!
\begin{aligned}&=\sum_{k=b_i}A_{k-b_i}*(k-1)!b_i*C_{a_i}^{bi}* (m-1-k)!\\ &=b_i*C_{a_i}^{bi}*\sum_{k=b_i}A_{k-b_i}*(k-1)!*(m-1-k)!\end{aligned}
=k=bi∑Ak−bi∗(k−1)!bi∗Caibi∗(m−1−k)!=bi∗Caibi∗k=bi∑Ak−bi∗(k−1)!∗(m−1−k)!其中
d
p
dp
dp复杂度是
O
(
n
∗
m
∗
b
j
)
O(n*m*b_j)
O(n∗m∗bj),后面统计答案还需要遍历
k
k
k,复杂度
O
(
n
∗
k
)
O(n*k)
O(n∗k),无法通过。
观察上面2个式子,均满足卷积性质,于是我们可以先通过分治NTT,预处理出
A
A
A,并记录分治的中间结果(因为
∑
b
i
≤
∑
a
i
=
m
\sum b_i\le \sum a_i=m
∑bi≤∑ai=m,所以时间和空间复杂度均为
O
(
m
log
2
2
(
m
)
)
O\big(m\log_2^2(m)\big)
O(mlog22(m))。
预处理出
A
A
A的分治结果后,再第二次通过分治,利用NTT结合第一次分治处理的
A
A
A结果,依次计算出每个玩家
i
i
i的获胜总方案数,因为这次是从上至下计算,
A
A
A的结果数量是逐渐减小的,所以每次NTT计算出来的结果有很多对一下层都是无用的,可以优化删掉,复杂度
O
(
m
log
2
2
(
m
)
)
O\big(m\log_2^2(m)\big)
O(mlog22(m))。
#include<bits/stdc++.h>
#define fi first
#define se second
#define lson (k<<1)
#define rson (k<<1)+1
#define mid ((l+r)/2)
#define sz(x) int(x.size())
#define pii pair<ll,ll>
#define bit bitset<100000>
using namespace std;
const int MAX=2e6+10;
const int MOD=998244353; //G=3
const int INF=INT_MAX/2;
const double PI=acos(-1.0);
typedef int64_t ll;
ll POW(ll x,ll n)
{
ll ret=1;
while(n)
{
if(n&1)ret=ret*x%MOD;
x=x*x%MOD;
n>>=1;
}
return ret;
}
void NTT(vector<ll>& p,ll len,int tag)
{
auto rev=[](ll x,ll len){
ll ret=0;
for(ll i=0;(1<<i)<len;i++)
{
ret<<=1;
if(x&(1<<i))ret|=1;
}
return ret;
};
for(int i=0;i<len;i++)
{
ll x=rev(i,len);
if(i<x)swap(p[i],p[x]);
}
for(int i=1;i<len;i<<=1)
{
ll wn=POW(3,(MOD-1)/(2*i));
if(tag==-1)wn=POW(wn,MOD-2);
for(int j=0;j<len;j+=i*2)
{
ll w=1;
for(int k=0;k<i;k++)
{
ll x=p[j+k];
ll y=w*p[j+k+i]%MOD;
p[j+k]=(x+y)%MOD;
p[j+k+i]=(x-y+MOD)%MOD;
w=w*wn%MOD;
}
}
}
if(tag==-1)for(int i=0,x=POW(len,MOD-2);i<len;i++)p[i]=p[i]*x%MOD;
}
vector<ll> mul(vector<ll> a,vector<ll> b)
{
int n=a.size();
int m=b.size();
ll len=1;
while(len<n+m)len*=2;
a.resize(len);
b.resize(len);
NTT(a,len,1);
NTT(b,len,1);
for(int i=0;i<len;i++)a[i]=a[i]*b[i]%MOD;
NTT(a,len,-1);
a.resize(n+m-1);
return a;
}
ll fac[MAX],inv[MAX];
ll C(int n,int m){return fac[n]*inv[m]%MOD*inv[n-m]%MOD;}
ll a[MAX],b[MAX];
vector<ll>A[MAX];
void init(int k,int l,int r) //预处理出dp结果
{
if(l==r)
{
for(int i=0;i<b[r];i++)A[k].push_back(C(a[r],i));
return;
}
init(lson,l,mid);
init(rson,mid+1,r);
A[k]=mul(A[lson],A[rson]);
}
vector<ll>ans;
void cal(int k,int l,int r,const vector<ll> &p)
{
if(l==r)return ans.push_back(C(a[r],b[r])%MOD*b[r]%MOD*p[b[r]-1]%MOD);
vector<ll> L=p,R=p;
reverse(A[lson].begin(),A[lson].end());
reverse(A[rson].begin(),A[rson].end());
L=mul(L,A[rson]);
R=mul(R,A[lson]);
L.erase(L.begin(),L.begin()+A[rson].size()-1);//删除无用结果
R.erase(R.begin(),R.begin()+A[lson].size()-1);
L.resize(A[lson].size()); //精简优化结果
R.resize(A[rson].size());
cal(lson,l,mid,L);
cal(rson,mid+1,r,R);
}
int solve()
{
int n,m;
cin>>n>>m;
fac[0]=inv[0]=inv[1]=1;
for(int i=1;i<=m;i++)fac[i]=fac[i-1]*i%MOD;
for(int i=2;i<=m;i++)inv[i]=inv[MOD%i]*(MOD-MOD/i)%MOD;
for(int i=1;i<=m;i++)inv[i]=inv[i]*inv[i-1]%MOD;
for(int i=1;i<=n;i++)scanf("%lld",a+i);
for(int i=1;i<=n;i++)scanf("%lld",b+i);
init(1,1,n);
vector<ll>p;
for(int i=0;i<m;i++)p.push_back(fac[i]*fac[m-1-i]%MOD);
cal(1,1,n,p);
for(ll i:ans)printf("%lld\n",i);
return 0;
}
int main()
{
int T=1;
//cin>>T;
while(T--)solve();
return 0;
}
J. Traveling Salesperson in an Island
题解
K. New Year Festival
思路:首先将所有
x
x
x坐标排序去重,可以想到最终状态所有事件会被分组,且每一组一定是由一件件首尾相接的事件组合而成,且该组事件的起点或终点一定是恰好位于某个
x
i
x_i
xi上。
n
≤
11
n\le11
n≤11,考虑状压dp,先预处理出
- l [ i ] [ S ] l[i][S] l[i][S],代表以 x i x_i xi为事件起点,事件状态为 S S S的一组事件的最小花费,转移方程为 l i , S = min k + 2 j = S ( l i , k + l c o s t j ) l_{i,S}=\min_{k+2^j=S}(l_{i,k}+lcost_j) li,S=k+2j=Smin(li,k+lcostj)
- r [ i ] [ S ] r[i][S] r[i][S],代表以 x i x_i xi为事件终点,事件状态为 S S S的一组事件的最小花费,转移方程为 r i , S = min k + 2 j = S ( r i , k + r c o s t j ) r_{i,S}=\min_{k+2^j=S}(r_{i,k}+rcost_j) ri,S=k+2j=Smin(ri,k+rcostj)
- f [ i ] [ j ] [ S ] f[i][j][S] f[i][j][S],代表在 [ x i , x j ] [x_i,x_j] [xi,xj]区间的两个端点处,安排状态为 S S S的所有事件的最小花费,转移方程为 f i , j , S = min g + h = S ( l i , g + r j , h ) f_{i,j,S}=\min_{g+h=S}(l_{i,g}+r_{j,h}) fi,j,S=g+h=Smin(li,g+rj,h)
预处理好后,我们用
d
[
i
]
[
S
]
d[i][S]
d[i][S]代表在区间
[
x
0
,
x
i
]
[x_0,x_i]
[x0,xi]内安排好状态为
S
S
S的所有事件的最小花费,来进行最后的dp处理,转移方程如下:
d
j
,
S
=
min
g
+
h
=
S
(
d
i
,
g
+
f
i
,
j
,
h
)
d_{j,S}=\min_{g+h=S} (d_{i,g}+f_{i,j,h})
dj,S=g+h=Smin(di,g+fi,j,h)
因为事件的结束时间可以超出
x
i
x_i
xi,所以最终答案为
a
n
s
=
min
g
+
h
=
2
n
−
1
d
i
,
g
+
l
i
,
h
ans=\min_{g+h=2^n-1} d_{i,g}+l_{i,h}
ans=g+h=2n−1mindi,g+li,h
其中枚举子集复杂度为
O
(
3
n
)
O(3^n)
O(3n),枚举区间端点复杂度为
O
(
∑
2
m
i
)
O(\sum^2 m_i)
O(∑2mi),总复杂度
O
(
3
n
∗
∑
2
m
i
)
O(3^n*\sum^2 m_i)
O(3n∗∑2mi)。
#include<bits/stdc++.h>
#define fi first
#define se second
#define lson (k<<1)
#define rson (k<<1)+1
#define mid ((l+r)/2)
#define sz(x) int(x.size())
#define pii pair<ll,ll>
#define bit bitset<100000>
using namespace std;
const int MAX=2e6+10;
const int MOD=998244353; //G=3
const int INF=INT_MAX;
const double PI=acos(-1.0);
typedef int64_t ll;
void Min(ll &x,ll y,ll z)
{
if(y<0||z<0)return;
if(x<0)x=y+z;
x=min(x,y+z);
}
struct lenka
{
ll m,d;
vector<pii>p;
void read(vector<ll> &s)
{
scanf("%lld%lld",&m,&d);
p.resize(m);
for(auto &kv:p)scanf("%lld%lld",&kv.fi,&kv.se);
for(auto &kv:p)s.push_back(kv.fi);
sort(s.begin(),s.end());
s.erase(unique(s.begin(),s.end()),s.end());
}
ll cost(ll x)
{
if(m==1)return x==p[0].fi?p[0].se:-1;
for(int j=0;j+1<m;j++)
{
ll k=(p[j+1].se-p[j].se)/(p[j+1].fi-p[j].fi);
if(x>=p[j].fi&&x<=p[j+1].fi)return p[j].se+k*(x-p[j].fi);
}
return -1;
}
}a[11];
ll d[60][1<<11];
ll f[60][1<<11];
ll l[60][1<<11];
ll r[60][1<<11];
ll sum[1<<11];
int solve()
{
vector<ll>s;
int n;
cin>>n;
for(int i=0;i<n;i++)a[i].read(s);
memset(d,-1,sizeof d);
memset(l,-1,sizeof l);
memset(r,-1,sizeof r);
memset(sum,0,sizeof sum);
for(int i=0;i<(1<<n);i++)
for(int j=0;j<n;j++)
{
if((1<<j)&i)sum[i]+=a[j].d;
}
for(int i=0;i<sz(s);i++)d[i][0]=l[i][0]=r[i][0]=0;
for(int i=0;i<sz(s);i++)
for(int j=0;j<(1<<n);j++)
{
ll len=0;
for(int k=0;k<n;k++)if((1<<k)&j)len+=a[k].d;
for(int k=0;k<n;k++)
{
if((1<<k)&j)continue;
Min(l[i][j+(1<<k)],l[i][j],a[k].cost(s[i]+len));
Min(r[i][j+(1<<k)],r[i][j],a[k].cost(s[i]-len-a[k].d));
}
}
for(int i=0;i<sz(s);i++)
{
memset(f,-1,sizeof f);
for(int j=i;j<sz(s);j++)f[j][0]=0;
for(int j=i;j<sz(s);j++)
for(int k=0;k<(1<<n);k++)
{
int S=(1<<n)-1-k;
for(int h=S;h;h=(h-1)&S)
{
if(s[j]-s[i]<sum[k+h])continue;
Min(f[j][k+h],l[i][k],r[j][h]);
}
if(s[j]-s[i]>=sum[k])Min(f[j][k],l[i][k],r[j][0]);
}
for(int j=i;j<sz(s);j++)
for(int k=0;k<(1<<n);k++)
{
int S=(1<<n)-1-k;
for(int h=S;h;h=(h-1)&S)Min(d[j][k+h],d[i][k],f[j][h]);
Min(d[j][k],d[i][k],f[j][0]);
}
}
ll ans=-1;
for(int i=0;i<sz(s);i++)
for(int j=0;j<(1<<n);j++)Min(ans,d[i][j],l[i][(1<<n)-1-j]);
return printf("%lld\n",ans);
}
int main()
{
int T=1;
//cin>>T;
while(T--)solve();
return 0;
}