20210626模拟赛
打开发现是 ASDFZ 的题,救命,我直接瑟瑟发抖。
经历了上一次考 ASDFZ 的题做四个半小时怒砍 10 分的惨痛经历,现在我听到这个学校就闻风丧胆(bushi
然而其实这次好温柔2333
最后结果是 T1 切了,T2T3 没时间看。
T1 手玩猜结论题(可能就我是猜的)。T2 是个矩乘,难点在算转移的系数。T3 交互。
占坑,要回家了,明天一定补题(确信
upata 6.27 补完坑了,话说回家看了一个通宵的欧洲杯,六点才睡差点猝死
通过手玩发现 ∏ ( 1 + x a i ) = ∏ ( 1 + x − a i ) \prod(1+x^{a_i})=\prod(1+x^{-a_i}) ∏(1+xai)=∏(1+x−ai) 成立,当且仅当 ∑ a i = 0 \sum a_i=0 ∑ai=0 。
然后你就猜想,找到原式的一个因式分解 F ( x ) = ∏ ( 1 + x a i ) F(x)=\prod(1+x^{a_i}) F(x)=∏(1+xai) 后,它其他任意的因式分解都是这个因式分解的若干个 a i a_i ai 变成 − a i -a_i −ai 的结果,且变了的 a i a_i ai 的和等于 0 0 0 。
然后你发现你猜对了,所以对原式因式分解后直接背包即可,时间复杂度 O ( n log n log ∑ y i ) O(n\log n\log {\sum y_i}) O(nlognlog∑yi)
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
struct node{
ll a,b,val;
};
vector<node> cc,B;
struct X{
ll e,y;
};
bool cmp_e(X a,X b){ return a.e<b.e; }
bool cmp_val(node a,node b){ return a.val<b.val; }
void get_nxt(vector<X> a,int lim);
bool chk(vector<X> a,int lim){
int l,r;
if(lim<0){
l=a.size()-1;
for(r=a.size()-1;r>=0;r--){
if(a[r].y==0&&a[r].e==0) return 0;
if(a[r].y==0) continue;
while(l>=0&&a[l].e>a[r].e+lim) l--;
if(l<0||a[l].e!=a[r].e+lim) return 0;
if(a[l].y<a[r].y) return 0;
a[l].y-=a[r].y;
}
return 1;
}
r=0;
for(l=0;l<a.size();l++){
if(a[l].y==0&&a[l].e==0) return 0;
if(a[l].y==0) continue;
while(r<a.size()&&a[r].e<a[l].e+lim) r++;
if(r==a.size()||a[r].e!=a[l].e+lim) return 0;
if(a[r].y<a[l].y) return 0;
a[r].y-=a[l].y;
}
return 1;
}
void work(vector<X> a,int mid,int l,int r){
// for(X i : a) cout<<i.e<<' '<<i.y<<' '<<l<<' '<<r<<endl;
// cout<<endl;
while(l>=0&&r<a.size()){
if(a[r].e-a[mid].e<=a[mid].e-a[l].e){
if(!chk(a,a[r].e-a[mid].e)) r++;
else{ get_nxt(a,a[r].e-a[mid].e); return; }
}
else{
if(!chk(a,a[l].e-a[mid].e)) l--;
else{ get_nxt(a,a[l].e-a[mid].e); return; }
}
}
while(l>=0){
if(!chk(a,a[l].e-a[mid].e)) l--;
else{ get_nxt(a,a[l].e-a[mid].e); return; }
}
while(r<a.size()){
if(!chk(a,a[r].e-a[mid].e)) r++;
else{ get_nxt(a,a[r].e-a[mid].e); return; }
}
}
void get_nxt(vector<X> a,int lim){
// cout<<lim<<endl;
vector<X> b;
int ql,qr,mid;
int l,r;
if(lim<0){
cc.push_back({0,1,-lim});
l=a.size()-1;
for(r=a.size()-1;r>=0;r--){
if(a[r].y==0) continue;
b.push_back(a[r]);
while(l>=0&&a[l].e>a[r].e+lim) l--;
a[l].y-=a[r].y;
}
// for(X i : b) cout<<b.e
reverse(&b[0],&b[b.size()]);
}
else{
cc.push_back({1,0,lim});
r=0;
for(l=0;l<a.size();l++){
if(a[l].y==0) continue;
b.push_back(a[l]);
while(r<a.size()&&a[r].e<a[l].e+lim) r++;
a[r].y-=a[l].y;
}
}
// for(X i : b) cout<<i.e<<' '<<i.y<<endl;
// cout<<endl;
if(b.size()==1) return;
lim=abs(lim);
ql=0; while(-b[ql].e>lim) ql++;
mid=0; while(b[mid].e!=0) mid++;
if(-b[ql].e<lim) ql--;
qr=b.size()-1; while(b[qr].e>lim) qr--;
if(b[qr].e<lim) qr++;
work(b,mid,ql,qr);
}
int n,m,cnt;
vector<X> ans,now;
int main(){
cin>>n;
vector<X> A;
ll u,v,sum=0;
for(int i=1;i<=n;i++) cin>>u>>v,A.push_back({u,v}),sum+=v;
int l=0; while(sum) l++,sum>>=1;
sort(&A[0],&A[A.size()],cmp_e);
int mid;
for(int i=0;i<n;i++) if(A[i].e==0) mid=i;
work(A,mid,mid-1,mid+1);
sort(&cc[0],&cc[cc.size()],cmp_val);
cc.push_back({0,0,-1});
for(int i=1;i<cc.size();i++){
if(cc[i].val==cc[i-1].val) cc[i].a+=cc[i-1].a,cc[i].b+=cc[i-1].b;
else B.push_back(cc[i-1]);
}
ans.push_back({0,1});
for(int i=0;i<B.size();i++){
int lim=ans.size();
for(ll j=1;j<=B[i].a;j++)
for(int k=0;k<lim;k++)
ans.push_back({ans[k].e+j*B[i].val,ans[k].y});
for(ll j=1;j<=B[i].b;j++)
for(int k=0;k<lim;k++)
ans.push_back({ans[k].e-j*B[i].val,ans[k].y});
now.clear();
sort(&ans[0],&ans[ans.size()],cmp_e);
for(int j=1;j<ans.size();j++){
if(ans[j].e==ans[j-1].e) ans[j].y+=ans[j-1].y;
else now.push_back(ans[j-1]);
}
now.push_back(ans[ans.size()-1]);
ans.clear();
swap(now,ans);
}
for(X i : ans) if(i.e==0) cout<<i.y<<endl;
}
考虑哈密顿路的结构,在第 i i i 层( i i i 越大就在越下面),可以先选出若干条点不相交的路径,再选出⼀些关键边 ( u , v ) (u,v) (u,v) ,走 ( i , u ) → ( i + 1 , u ) (i,u) \to (i+1,u) (i,u)→(i+1,u) 这条边,然后在 i i i 下方的层走若干步后,再从 ( i + 1 , v ) → ( i , v ) (i+1,v)\to(i,v) (i+1,v)→(i,v) 返回第 i i i 层。显然选的关键边不能有公共端点。
则如果第 i i i 层选出了 c c c 条向下走的关键边,那么第 i + 1 i+1 i+1 层就需要选出 c c c 条不相交的路径。
我们设状态 f [ i ] [ j ] f[i][j] f[i][j] 表示 N × i N\times i N×i 的哈密顿回路的第 i i i 层选了 j j j 条关键边的个数。
那么有转移 f [ i ] [ j ] = ∑ f [ i − 1 ] [ k ] × p [ k ] [ j ] f[i][j]=\sum f[i-1][k]\times p[k][j] f[i][j]=∑f[i−1][k]×p[k][j] 。其中 p [ k ] [ j ] p[k][j] p[k][j] 表示上一层有 k k k 个关键边,然后在这一层选了 k k k 条不相交的路径,再选 j j j 条关键边的方案数。
显然我们把 p p p 求出来后,答案就能用矩阵快速幂解决。
所以接下来考虑求 p [ k ] [ j ] p[k][j] p[k][j] 。
首先把可动的点排个序,方案数为 ( n − 2 k ) ! (n-2k)! (n−2k)! ,然后我们想象把这一层的 k k k 条不相交的路径依次用 k − 1 k-1 k−1 条边连起来,于是一共有了 n − 1 n-1 n−1 条边,构成了一条链。
然后在链上把这一层选的 j j j 条关键边看成红色,把连接路径的 k − 1 k-1 k−1 条边看成蓝色,剩下的 ( n − 1 ) − j − ( k − 1 ) (n-1)-j-(k-1) (n−1)−j−(k−1) 条非关键边看成白色。
你发现把可动的点消序过后,任意一个选路径再选关键边的方案都对应唯一一个链上的颜色序列。于是我们转而来求合法的颜色序列的个数。
合法的颜色序列满足:序列有 n − 1 n-1 n−1 个元素,其中有 j j j 个红色, k − 1 k-1 k−1 个蓝色, n − j − k n-j-k n−j−k 个白色,序列中不能有相邻的两个蓝色,不能有相邻的两个红色,且蓝色不能再首尾。这个可以简单 d p dp dp 出来。
所以 p [ k ] [ j ] = p[k][j]= p[k][j]= 合法的颜色序列方案数 × ( n − 2 k ) ! \times(n-2k)! ×(n−2k)! 。
最后就可以快乐矩阵乘法啦,时间复杂度 O ( T n 3 log L ) O(Tn^3\log L) O(Tn3logL) 。
#include <bits/stdc++.h>
#define N 251
using namespace std;
const int mod=1e9+7;
typedef long long ll;
int n,nn,L,C[N<<1][N<<1],fac[N<<1];
int g[N<<1][N][N][3];
struct M{
ll a[N][N];
}ans,p,o;
M operator *(M x,M y){
M res; memset(res.a,0,sizeof(res.a));
for(register int i=0;i<=nn;i++)
for(register int k=0;k<=nn;k++)
for(register int j=0;j<=nn;j++)
res.a[i][j]=res.a[i][j]+x.a[i][k]*y.a[k][j]%mod;
for(register int i=0;i<=nn;i++) for(register int j=0;j<=nn;j++) res.a[i][j]%=mod;
return res;
}
void work(){
cin>>n>>L; L--;
nn=n>>1;
memset(ans.a,0,sizeof(ans.a));
memset(p.a,0,sizeof(p.a));
for(int a=1;a<=nn;a++){
p.a[a][0]=1ll*C[n-a-1][a-1]*fac[n-(a<<1)]%mod;
for(int b=1;b<=nn;b++){
if(n-b<a) continue;
p.a[a][b]=1ll*(1ll*g[n-1][b][a-1][0]+g[n-1][b][a-1][1])*fac[n-(a<<1)]%mod;
}
}
if(n==2) ans.a[1][1]=2;
else
for(int i=1;i<=nn;i++)
ans.a[1][i]=1ll*fac[n-1]*(1ll*g[n-1][i][0][0]+g[n-1][i][0][1]+g[n-3][i-1][0][0]+g[n-3][i-1][0][1])%mod;
M res=o; int cnt=0;
while(L){
if(L&1) res=res*p;
p=p*p; L>>=1;
}
ans=ans*res;
cout<<ans.a[1][0]<<endl;
}
void init(){
n=500; nn=250;
g[0][0][0][0]=g[1][0][0][0]=g[1][1][0][1]=1;
for(int i=2;i<=n;i++)
for(int j=0;j<=nn;j++)
for(int k=0;k<=nn;k++){
g[i][j][k][0]=(1ll*g[i-1][j][k][0]+g[i-1][j][k][1]+g[i-1][j][k][2])%mod;
if(j) g[i][j][k][1]=(1ll*g[i-1][j-1][k][0]+g[i-1][j-1][k][2])%mod;
if(k) g[i][j][k][2]=(1ll*g[i-1][j][k-1][0]+g[i-1][j][k-1][1])%mod;
}
C[0][0]=1;
for(int i=1;i<=n;C[i][0]=1,i++)
for(int j=1;j<=i;j++)
C[i][j]=(C[i-1][j]+C[i-1][j-1])%mod;
fac[0]=1;
for(int i=1;i<=n;i++) fac[i]=1ll*fac[i-1]*i%mod;
for(int i=0;i<=nn;i++) o.a[i][i]=1;
}
int main(){
freopen("test.in","r",stdin);
init();
int T; cin>>T;
while(T--) work();
}
看题解看懂的,没有其他特别的思路
#include "guess.h"
#include <cstdio>
#include <algorithm>
#include <vector>
#include <numeric>
#include <random>
#include <cassert>
using namespace std;
#define all(cont) begin(cont), end(cont)
#define LOG(f...) fprintf(stderr, f)
random_device _rd;
mt19937 rng;
const int N = 3005;
vector<int> path[N];
vector<int> light[N];
bool vis[N];
void insert_path(vector<int> &P, int x) {
auto it = lower_bound(begin(P), end(P), x, [](int x, int y) {
return query({x, y}) == x;
});
P.insert(it, x);
}
void solve(int n, int L, int testid) {
if (testid == 1) {
vector<int> a(n), res;
iota(begin(a), end(a), 1);
while ((int)a.size() >= 3) {
int lst = query(a);
auto it = find(begin(a), end(a), lst);
res.push_back(*it);
a.erase(it);
}
report(a[0], res.back());
for (size_t i = 1; i < res.size(); ++i)
report(res[i - 1], res[i]);
report(res.front(), a.back());
}
else {
vector<int> perm(n - 1), res;
iota(begin(perm), end(perm), 2);
shuffle(begin(perm), end(perm), rng);
fill(vis + 1, vis + 1 + n, false);
for (int i = 1; i <= n; ++i)
path[i].clear(), light[i].clear();
for (int x : perm) {
if (vis[x])
continue;
vis[x] = true;
int rt = 1;
while (true) {
if (path[rt].empty()) {
path[rt].push_back(x);
break;
}
int lca = query({path[rt].back(), x});
vis[lca] = true;
if (lca == x) {
insert_path(path[rt], x);
break;
}
else if (lca == path[rt].back()) {
path[rt].push_back(x);
break;
}
else if (lca != rt && find(all(path[rt]), lca) == end(path[rt]))
insert_path(path[rt], lca);
if (light[lca].empty()) {
light[lca].push_back(x);
break;
}
light[lca].push_back(x);
int ch = query(light[lca]);
light[lca].pop_back();
if (ch == lca) {
light[lca].push_back(x);
break;
}
if (ch != x && find(all(light[lca]), ch) != end(light[lca])) {
rt = ch;
continue;
}
vis[ch] = true;
int L = 0, R = light[lca].size() - 2, res = light[lca].size() - 1;
while (L <= R) {
int mid = (L + R) >> 1;
vector<int> tmp(begin(light[lca]), begin(light[lca]) + mid + 1);
tmp.push_back(ch);
if (query(tmp) == ch)
res = mid, R = mid - 1;
else
L = mid + 1;
}
int id = light[lca][res];
path[ch] = path[id];
path[ch].insert(begin(path[ch]), id);
path[id].clear();
light[lca][res] = ch;
if (x != ch)
light[ch].push_back(x);
break;
}
}
for (int i = 1; i <= n; ++i) {
if (!path[i].empty()) {
report(i, path[i].front());
for (size_t j = 1; j < path[i].size(); ++j)
report(path[i][j - 1], path[i][j]);
}
for (int v : light[i])
report(i, v);
}
}
}