20210626模拟赛

打开发现是 ASDFZ 的题,救命,我直接瑟瑟发抖。

经历了上一次考 ASDFZ 的题做四个半小时怒砍 10 分的惨痛经历,现在我听到这个学校就闻风丧胆(bushi

然而其实这次好温柔2333

最后结果是 T1 切了,T2T3 没时间看。

T1 手玩猜结论题(可能就我是猜的)。T2 是个矩乘,难点在算转移的系数。T3 交互。

占坑,要回家了,明天一定补题(确信

upata 6.27 补完坑了,话说回家看了一个通宵的欧洲杯,六点才睡差点猝死


T1

通过手玩发现 ∏ ( 1 + x a i ) = ∏ ( 1 + x − a i ) \prod(1+x^{a_i})=\prod(1+x^{-a_i}) (1+xai)=(1+xai) 成立,当且仅当 ∑ 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(nlognlogyi)

#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;
}

T2

考虑哈密顿路的结构,在第 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[i1][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)! (n2k)! ,然后我们想象把这一层的 k k k 条不相交的路径依次用 k − 1 k-1 k1 条边连起来,于是一共有了 n − 1 n-1 n1 条边,构成了一条链。

然后在链上把这一层选的 j j j 条关键边看成红色,把连接路径的 k − 1 k-1 k1 条边看成蓝色,剩下的 ( n − 1 ) − j − ( k − 1 ) (n-1)-j-(k-1) (n1)j(k1) 条非关键边看成白色。

你发现把可动的点消序过后,任意一个选路径再选关键边的方案都对应唯一一个链上的颜色序列。于是我们转而来求合法的颜色序列的个数。

合法的颜色序列满足:序列有 n − 1 n-1 n1 个元素,其中有 j j j 个红色, k − 1 k-1 k1 个蓝色, n − j − k n-j-k njk 个白色,序列中不能有相邻的两个蓝色,不能有相邻的两个红色,且蓝色不能再首尾。这个可以简单 d p dp dp 出来。

所以 p [ k ] [ j ] = p[k][j]= p[k][j]= 合法的颜色序列方案数 × ( n − 2 k ) ! \times(n-2k)! ×(n2k)!

最后就可以快乐矩阵乘法啦,时间复杂度 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();
}

T3

看题解看懂的,没有其他特别的思路
在这里插入图片描述

#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);
    }
  }
}
posted @ 2022-10-10 20:18  缙云山车神  阅读(19)  评论(0编辑  收藏  举报