长乐集训集合

一些结论题和码农题不在此列。
赛时切的也不列入。

#504. 「插头 DP」方格填写

\(f(x)^2\)看作两张图,其最后状态一样的方案数。

直接对其进行插头dp计算这种方案数即可,考虑按前缀贡献进行分类讨论刷表转移。

方格填写
// code by fhq_treap
#include<bits/stdc++.h>
#define ll long long
#define N 20005
#define mod 998244353

inline ll read(){
    char C=getchar();
    ll A=0 , F=1;
    while(('0' > C || C > '9') && (C != '-')) C=getchar();
    if(C == '-') F=-1 , C=getchar();
    while('0' <= C && C <= '9') A=(A << 1)+(A << 3)+(C - 48) , C=getchar();
    return A*F;
}

template <typename T>
void write(T x)
{
    if(x < 0) {
        putchar('-');
        x = -x;
    }
    if(x > 9)
        write(x/10);
    putchar(x % 10 + '0');
    return;
}

int n,m;

ll f[2][10][(1 << 8)][(1 << 8)];

int a[100][20];

int c[4];

inline void del(int &x,int L,int k,int w){
	x = L;
	if(x & (1 << m))
	x ^= (1 << m);
	if(k == 0)
	if(x & (1 << (w - 1)))
	x ^= (1 << (w - 1));
	if(k == 1){
	if(x & (1 << (w - 1)))
	x ^= (1 << (w - 1));
	x |= (1 << m);
	}
	if(k == 2)
	x |= (1 << (w - 1));
	if(k == 3){
	x |= (1 << (w - 1));
	x |= (1 << m);
	}
}

inline void print(int x){
	for(int i = 0;i <= m;++i)
	std::cout<<((x & (1 << i)) > 0)<<" ";
	puts("");
}

int main(){
	c[0] = 0,c[1] = 1,c[2] = 1,c[3] = 2;
	freopen("grid.in","r",stdin);
	freopen("grid.out","w",stdout);
	int T;
	scanf("%d",&T);
	while(T -- ){
	memset(f,0,sizeof(f));
	n = read(),m = read();
	for(int i = 1;i <= n;++i)
	for(int j = 1;j <= m;++j)
	a[i][j] = read();
	f[0][m][0][0] = 1;
	for(int i = 1;i <= n;++i)
	for(int j = 1;j <= m;++j){
        int now = i & 1;//现在的表
        int lasx = i & 1;
        int lasy = j - 1;
        if(lasy == 0)lasy = m,lasx ^= 1;
//        std::cout<<"("<<i<<","<<j<<")"<<"'s las "<<"("<<lasx<<","<<lasy<<")"<<std::endl;
        for(int lasA = 0;lasA < (1 << (m + 1));++lasA)
        for(int lasB = 0;lasB < (1 << (m + 1));++lasB){//A B两个图的表
			if(f[lasx][lasy][lasA][lasB]){
//			puts("LASA");
//			print(lasA);
//			puts("LASB");
//			print(lasB);
			int cA = ((lasA & (1 << m)) > 0) + ((lasA & (1 << (j - 1))) > 0);
			int cB = ((lasB & (1 << m)) > 0) + ((lasB & (1 << (j - 1))) > 0);//前面的度数
//			std::cout<<"lasCNT"<<" "<<cA<<" "<<cB<<std::endl;
//			std::cout<<"THE CNT OF "<<f[lasx][lasy][lasA][lasB]<<std::endl;
			for(int A = 0;A < 4;++A)
			for(int B = 0;B < 4;++B){
				if(j == m && ((A != 0 && A != 2) || (B != 0 && B != 2)))
				continue;
				if(i == n && ((A != 0 && A != 1) || (B != 0 && B != 1)))
				continue;
//				std::cout<<"TRY "<<A<<" "<<B<<" "<<cA + c[A]<<" "<<cB + c[B]<<std::endl;
				if(cA + c[A] == cB + c[B]){
					if(cA + c[A] != a[i][j] && a[i][j] != -1)
					continue;
					if(cB + c[B] != a[i][j] && a[i][j] != -1)
					continue;
					int tA,tB;
//                    std::cout<<"USE "<<A<<" "<<B<<std::endl;
					del(tA,lasA,A,j);
					del(tB,lasB,B,j);
//					print(tA);
//                    print(tB);
//                    puts("");
					f[now][j][tA][tB] = (f[now][j][tA][tB] + f[lasx][lasy][lasA][lasB]);
					if(f[now][j][tA][tB] > mod)
					f[now][j][tA][tB] -= mod;
				}
			}
//			puts("");
//			puts("");
			}
		}
//		std::cout<<"ready to print the ans"<<std::endl;
//		for(int lasA = 0;lasA < (1 << (m + 1));++lasA)
//        for(int lasB = 0;lasB < (1 << (m + 1));++lasB){
//        	if(!f[now][j][lasA][lasB])continue;
//            puts("A");
//            print(lasA);
//            puts("B");
//            print(lasB);
//			std::cout<<f[now][j][lasA][lasB]<<std::endl;
//			puts("");
//		}
		for(int lasA = 0;lasA < (1 << (m + 1));++lasA)
        for(int lasB = 0;lasB < (1 << (m + 1));++lasB)
        f[now ^ 1][j][lasA][lasB] = 0;
	}
	std::cout<<f[n & 1][m][0][0]<<std::endl;
	}
}


#535. 「后缀数组」相似子串

考虑这类对子状物相同的判断,我们直接记录最小子状物的状态。

本题即记录前缀相同最近的距离即可。

然后每种的第一个使用\(-1\)代替。

考虑对这些后缀构成的串进行排序然后计算LCP即可解决原题。

思考如何进行快速排序。

有两类思路:

使用数据结构,我们可以使用可持久化分块数组即可。

利用字符集大小,字符集大小只有10,我们直接按-1的位置分类,比较两两的对。

「后缀数组」相似子串
#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Rg register
#define RI Rg int
#define Cn const
#define CI Cn int&
#define I inline
#define W while
#define N 100000
#define LG 17
using namespace std;
int n,s[N+5],lst[10];char st[N+5];
namespace SA
{
	int SA[N+5],rk[N+5],p[N+5],t[N+5];I void Sort(CI S)
	{
		RI i;for(i=0;i<=S;++i) t[i]=0;for(i=1;i<=n;++i) ++t[rk[i]];
		for(i=1;i<=S;++i) t[i]+=t[i-1];for(i=n;i;--i) SA[t[rk[p[i]]]--]=p[i];
	}
	I void GetSA()
	{
		RI i,k,t=0,S=n;for(i=1;i<=n;++i) rk[p[i]=i]=s[i];for(Sort(S),k=1;t^n;k<<=1,S=t)
		{
			for(t=0,i=1;i<=k;++i) p[++t]=n-k+i;for(i=1;i<=n;++i) SA[i]>k&&(p[++t]=SA[i]-k);for(Sort(S),i=1;i<=n;++i) p[i]=rk[i];
			for(rk[SA[1]]=t=1,i=2;i<=n;++i) (p[SA[i]]^p[SA[i-1]]||p[SA[i]+k]^p[SA[i-1]+k])&&++t,rk[SA[i]]=t;
		}
	}
	int H[N+5],Lg[N+5],Mn[N+5][LG+1];I void GetH()
	{
		RI i,j,k=0;for(i=1;i<=n;++i) rk[SA[i]]=i;
		for(i=1;i<=n;++i) if(k&&--k,rk[i]^1) {j=SA[rk[i]-1];W(i+k<=n&&j+k<=n&&s[i+k]==s[j+k]) ++k;H[rk[i]]=k;}
		for(Lg[0]=-1,i=2;i<=n;++i) Lg[i]=Lg[i>>1]+1,Mn[i][0]=H[i];
		for(j=1;(1<<j)<=n;++j) for(i=2;i+(1<<j)-1<=n;++i) Mn[i][j]=min(Mn[i][j-1],Mn[i+(1<<j-1)][j-1]);		
	}
	I int LCP(CI x,CI y)
	{
		if(x==y) return 1e9;RI l=rk[x],r=rk[y];l>r&&(swap(l,r),0),++l;
		RI k=Lg[r-l+1];return min(Mn[l][k],Mn[r-(1<<k)+1][k]);
	}
}
namespace SS
{
	int nxt[N+5][10],ct[N+5],q[N+5][11],lst[N+5],id[N+5],pos[N+5];
	I int Calc(CI x,CI y)
	{
		RI i=1,ans=0,wx=x,wy=y,nx,ny,t,o;W(wx<=n&&wy<=n)
		{
			nx=i<=ct[x]?q[x][i]:n+1,ny=i<=ct[y]?q[y][i]:n+1,t=min(nx-wx,ny-wy);if((o=SA::LCP(wx,wy))<t) return ans+o;
			if(nx^(wx+t)||ny^(wy+t)) return ans+t;ans+=t;if(nx>n||ny>n) return ans;++ans,wx=nx+1,wy=ny+1,++i;
		}return ans;
	}
	I bool cmp(CI x,CI y)
	{
		RI i,t=Calc(x,y),vx=x+t<=n?s[x+t]:-1,vy=y+t<=n?s[y+t]:-1;
		for(i=1;i<=ct[x];++i) q[x][i]==x+t&&(vx=0);for(i=1;i<=ct[y];++i) q[y][i]==y+t&&(vy=0);return vx<vy;
	}
	int Lg[N+5],Mn[N+5][LG+1];I void Build()
	{
		RI i,j;for(i=n;i;--i)
		{
			for(j=0;j<=9;++j) nxt[i][j]=nxt[i+1][j];nxt[i][st[i]&15]=i;
			for(j=0;j<=9;++j) nxt[i][j]&&(q[i][++ct[i]]=nxt[i][j]);sort(q[i]+1,q[i]+ct[i]+1);
		}
		for(i=1;i<=n;++i) id[i]=i;for(stable_sort(id+1,id+n+1,cmp),i=1;i<=n;++i) pos[id[i]]=i;
		for(Lg[0]=-1,i=2;i<=n;++i) Lg[i]=Lg[i>>1]+1,Mn[i][0]=Calc(id[i-1],id[i]);
		for(j=1;(1<<j)<=n;++j) for(i=2;i+(1<<j)-1<=n;++i) Mn[i][j]=min(Mn[i][j-1],Mn[i+(1<<j-1)][j-1]);
	}
	I int Q(CI l,CI r) {if(l>r) return 1e9;RI k=Lg[r-l+1];return min(Mn[l][k],Mn[r-(1<<k)+1][k]);}
	I int Qry(RI x,CI s)
	{
		RI l,r,mid;x=pos[x];
		l=1,r=x;W(l^r) mid=l+r-1>>1,Q(mid+1,x)>=s?r=mid:l=mid+1;RI o=r;
		l=x,r=n;W(l^r) mid=l+r+1>>1,Q(x+1,mid)>=s?l=mid:r=mid-1;return l-o+1;
	}
}
int main()
{
	freopen("similar.in","r",stdin),freopen("similar.out","w",stdout); 
	RI Qt,i;for(scanf("%d%d%s",&n,&Qt,st+1),i=1;i<=n;++i) s[i]=lst[st[i]&15]?i-lst[st[i]&15]:0,lst[st[i]&15]=i;
	SA::GetSA(),SA::GetH(),SS::Build();
	RI x,y,lst=0;W(Qt--) scanf("%d%d",&x,&y),x^=lst,y^=lst,printf("%d\n",lst=SS::Qry(x,y-x+1));return 0;
}

#825. 「计算几何初探」三角查找

考虑枚举底如何快速计算高是否有。

我们把这条边旋转为y轴,并考虑按x的偏序二分。

我们发现按x偏序的话,两点偏序关系只与我们枚举的底的斜率和两点构成的斜率有关。

对斜率排序,发现我们按大小枚举斜率时,只有枚举的这条边的两点偏序关系改变,把他们两个交换即可。

「计算几何初探」三角查找
#include <bits/stdc++.h>
#define int long long
using namespace std;
#define N 5005
struct hehe{
	int x, y;
}a[N];
struct haha{
	int a, b;
	hehe p;
}e[N * N];
int pos[N], rk[N];
bool cmp1(hehe x, hehe y)
{
	return x.x == y.x ? x.y < y.y : x.x < y.x;
}
bool cmp2(haha x, haha y)
{
	return x.p.x * y.p.y - x.p.y * y.p.x > 0;
}
hehe xl(hehe x, hehe y)
{
	hehe ret;
	ret.x = x.x - y.x;
	ret.y = x.y - y.y;
	return ret;
}
int cj(hehe x, hehe y)
{
	return x.x * y.y - x.y * y.x;
}
signed main()
{
	freopen("triangle.in","r",stdin);
	freopen("triangle.out","w",stdout);
	int n, cnt = 0, s;
	cin >> n >> s;
	for(int i = 1; i <= n; i++)
	{
		cin >> a[i].x >> a[i].y;
	}
	sort(a + 1, a + n + 1, cmp1);
	s *= 2;
	for(int i = 1; i <= n; i++)
	{
		for(int j = 1; j < i; j++)
		{
			hehe qwq;
			qwq.x = a[i].x - a[j].x;
			qwq.y = a[i].y - a[j].y;
			e[++cnt].a = j;
			e[cnt].b = i;
			e[cnt].p = qwq;
		}
	}
	sort(e + 1, e + cnt + 1, cmp2);
	for(int i = 1; i <= n; i++) rk[i] = pos[i] = i;
	for(int i = 1; i <= cnt; i++)
	{
		hehe p = e[i].p;
		int x = e[i].a, y = e[i].b;
		if(rk[x] > rk[y]) swap(x, y);
		int l = 1, r = rk[x] - 1;
		while(l <= r)
		{
			int mid = (l + r) >> 1;
			int si = abs(cj(p, xl(a[pos[mid]], a[pos[rk[x]]])));
			if(si == s)
			{
				cout << "Yes" << endl << a[x].x << ' ' << a[x].y << endl << a[y].x << ' ' << a[y].y << endl << a[pos[mid]].x << ' ' << a[pos[mid]].y;
				exit(0);
			}
			else if(si > s) l = mid + 1;
			else r = mid - 1;
		}
		swap(rk[x], rk[y]);
		swap(pos[rk[x]], pos[rk[y]]);
	}
	cout << "No" << endl;
	return 0;
}

#981. 「prufer编码」森林之和

考虑先思考如何计数一颗树的贡献。

我们发现在其为无根树,我们不妨为其确定根,则所有点只在作为根时贡献,其贡献和相等。

那么我们不妨强制\(1\)为根。

考虑如何确定其。

我们设\(f_{i,j}\)\(1\)的儿子为\(j\)个的整体树大小为\(i\)的树方案。

考虑\(purfer\)序列,其1的度为\(j\),那么我们选取\(j - 1\)个位置,剩下随便填即可。

\(f_{i,j} = \binom{i - 2}{j - 1} * (i - j - 1) ^ {i - 1}\)

\(g_i\)为大小为\(i\)的树的所有贡献。

那么有\(g_i = i * \sum_{c = 1} f_{i,c} * c^2\)

再考虑回到原问题,我们强制枚举森林里的块大小,再算贡献

那么有\(ans = \sum_{i = 1} g_i * S(n - i)\)

其中\(S(n)\)\(n\)个点组成的森林大小。

枚举1所在的联通块那么有\(S(n) = \sum_i \binom{n}{i - 1} * (i) ^ {i - 2} * S(n - i)\)

「prufer编码」森林之和
// code by fhq_treap
#include <bits/stdc++.h>
#define ll long long
#define N 6005

inline ll read() {
    char C = getchar();
    ll A = 0, F = 1;
    while (('0' > C || C > '9') && (C != '-')) C = getchar();
    if (C == '-')
        F = -1, C = getchar();
    while ('0' <= C && C <= '9') A = (A << 1) + (A << 3) + (C - 48), C = getchar();
    return A * F;
}

template <typename T>
void write(T x) {
    if (x < 0) {
        putchar('-');
        x = -x;
    }
    if (x > 9)
        write(x / 10);
    putchar(x % 10 + '0');
    return;
}

int mod;

inline ll qpow(ll a, ll b) {
    ll ans = 1;
    while (b) {
        if (b & 1)
            ans = ans * a % mod;
        a = a * a % mod;
        b >>= 1;
    }
    return ans;
}

int s[N], inv[N];

int f[N][N];  //按照1~n编号加入的顺序,1号点的度数为x的方案数

int g[N];

int q[N];

int p[N][N];

int n = 5e3;

int T;

int c[N][N];

inline ll C(int x, int y) {
    if (c[x][y])
        return c[x][y];
    return c[x][y] = 1ll * s[x] * inv[y] % mod * inv[x - y] % mod;
}

int Ans[N];

int main() {
    freopen("forest.in", "r", stdin);
    freopen("forest.out", "w", stdout);
    scanf("%d", &T);
    scanf("%d", &mod);

    s[0] = 1;
    for (int i = 1; i <= n; ++i) s[i] = 1ll * s[i - 1] * i % mod;

    inv[n] = qpow(s[n], mod - 2);

    for (int i = 1; i <= n; ++i) {
        p[i][0] = 1;
        for (int j = 1; j <= n; ++j) {
            p[i][j] = 1ll * p[i][j - 1] * i % mod;
        }
    }

    for (int i = n - 1; i >= 0; --i) {
        inv[i] = 1ll * inv[i + 1] * (i + 1) % mod;
        //		std::cout<<s[i]<<" "<<inv[i]<<" "<<1ll * inv[i] * s[i] % mod<<std::endl;
    }

    for (int i = 1; i <= n; ++i)
        for (int j = 1; j < i; ++j) {
            f[i][j] = (1ll * C(i - 2, j - 1) % mod * p[i - 1][i - j - 1]) % mod;
            //		std::cout<<i<<" "<<j<<" "<<f[i][j]<<std::endl;
        }

    for (int i = 1; i <= n; ++i) {
        for (int j = 1; j <= i - 1; ++j) {
            g[i] = (g[i] + 1ll * f[i][j] * j % mod * j % mod);  // g_i : i点1的贡献
            if (g[i] > mod)
                g[i] -= mod;
        }
    }

    for (int i = 1; i <= n; ++i) g[i] = (1ll * g[i] % mod * i) % mod;

    q[0] = 1;

    for (int i = 1; i <= n; ++i) {
        for (int j = 1; j <= i; ++j) {
            q[i] = (q[i] + 1ll * C(i - 1, j - 1) * (j - 2 < 0 ? 1 : p[j][j - 2]) % mod * q[i - j] % mod);
            if (q[i] > mod)
                q[i] -= mod;
            //			std::cout<<j<<"->"<<i<<" "<<(i - 2 < 0 ? 1 : p[i][i - 2])<<"
            //"<<q[i]<<std::endl;
        }
        //		std::cout<<i<<" "<<q[i]<<std::endl;
    }

    while (T--) {
        n = read();
        if (Ans[n]) {
            write(Ans[n]);
            puts("");
            break;
        }
        ll ans = 0;
        for (int i = 1; i <= n; ++i) {
            ans = (ans + 1ll * g[i] * C(n, i) % mod * q[n - i] % mod) % mod;
        }
        write(Ans[n] = ans);
        puts("");
    }
}

/*
1000000007
*/

#584. 「网络流」欧拉回路

考虑二分答案。

那么有一些边被唯一定向,有一些边没有。

考虑如何对其判断是否有欧拉回路。

不妨先对其任意定向,然后考虑如何调整。

考虑设\(\delta_i\)为入度减出度。

由于可以翻转边\(\delta_x + 2,\delta_y - 2\)

考虑使用网络流解决其。

我们从原点往每个\(\delta_i > 0\)的点连一条\(\frac{\delta_i}{2}\)的边,\(\delta_i < 0\)汇点连一条\(\frac{|\delta_i|}{2}\)的边,可翻转的边\(y \to x\ in\ [1]\)

是否有欧拉回路即看是否流量满流。

「网络流」欧拉回路
#include <bits/stdc++.h>
#include <bits/extc++.h>
#define ll long long
#define ull unsigned ll
#define lowbit(x) (x & (-x))
template <typename T>
inline void read(T &x)
{
    x = 0;
    char s = (char)getchar();
    bool f = false;
    while (!(s >= '0' && s <= '9'))
    {
        if (s == '-')
            f = true;
        s = (char)getchar();
    }
    while (s >= '0' && s <= '9')
    {
        x = (x << 1) + (x << 3) + s - '0';
        s = (char)getchar();
    }
    if (f)
        x = (~x) + 1;
}
template <typename T, typename... T1>
inline void read(T &x, T1 &...x1)
{
    read(x);
    read(x1...);
}
template <typename T>
inline void ckmin(T &x, T y)
{
    if (x > y)
        x = y;
}
template <typename T>
inline void ckmax(T &x, T y)
{
    if (x < y)
        x = y;
}
using namespace std;
const int N = 5e4 + 5, M = 1e5 + 5;
int n, m;
struct node
{
    int u, v, w1, w2;
} e[N];
struct Edge
{
    int next, to, cap, flow;
} edge[N];
int head[N], num_edge = 1;
inline void add_edge(int from, int to, int cap, bool flag = true)
{
    edge[++num_edge].next = head[from];
    edge[num_edge].to = to;
    edge[num_edge].cap = cap;
    edge[num_edge].flow = 0;
    head[from] = num_edge;
    if (flag)
        add_edge(to, from, 0, false);
}
int dis[N], cur[N];
int S, T;
inline bool bfs()
{
    memcpy(cur, head, sizeof(cur));
    memset(dis, 0, sizeof(dis));
    queue<int> q;
    q.push(S);
    dis[S] = 1;
    while (!q.empty())
    {
        int u = q.front();
        q.pop();
        for (int i = head[u]; i; i = edge[i].next)
        {
            int &v = edge[i].to;
            if (!dis[v] && edge[i].cap > edge[i].flow)
            {
                dis[v] = dis[u] + 1;
                q.push(v);
            }
        }
    }
    return dis[T];
}
inline int dinic(int u, int flow)
{
    if (u == T)
        return flow;
    int res = 0;
    for (int &i = cur[u]; i; i = edge[i].next)
    {
        int &v = edge[i].to;
        if (dis[v] == dis[u] + 1 && edge[i].cap > edge[i].flow)
        {
            int f = dinic(v, min(flow, edge[i].cap - edge[i].flow));
            if (f)
            {
                res += f;
                flow -= f;
                edge[i].flow += f;
                edge[i ^ 1].flow -= f;
                if (!flow)
                    break;
            }
        }
    }
    return res;
}
inline int maxflow()
{
    int res = 0;
    while (bfs())
        res += dinic(S, INT_MAX);
    return res;
}
int deg[N];
inline bool check(int val)
{
    num_edge = 1;
    memset(head, 0, sizeof(head));
    memset(deg, 0, sizeof(deg));
    for (int i = 1; i <= m; ++i)
    {
        if (e[i].w1 <= val)
        {
            ++deg[e[i].u];
            --deg[e[i].v];
            if (e[i].w2 <= val)
                add_edge(e[i].u, e[i].v, 1);
        }
    }
    int sum = 0;
    for (int i = 1; i <= n; ++i)
    {
        if (deg[i] & 1)
            return false;
        deg[i] /= 2;
        if (deg[i] > 0)
        {
            add_edge(S, i, deg[i]);
            sum += deg[i];
        }
        else if (deg[i] < 0)
            add_edge(i, T, -deg[i]);
    }
    return sum == maxflow();
}
int id[N];
list<pair<int, int>> g[N];
inline void print(int u)
{
    while (!g[u].empty())
    {
        int v = g[u].back().first, w = g[u].back().second;
        g[u].pop_back();
        print(v);
        printf("%d ", w);
    }
}
inline void solve(int val)
{
    num_edge = 1;
    memset(head, 0, sizeof(head));
    memset(deg, 0, sizeof(deg));
    for (int i = 1; i <= m; ++i)
    {
        if (e[i].w1 <= val)
        {
            ++deg[e[i].u];
            --deg[e[i].v];
            if (e[i].w2 <= val)
            {
                add_edge(e[i].u, e[i].v, 1);
                id[i] = num_edge - 1;
            }
            else
                id[i] = -1;
        }
    }
    int sum = 0;
    for (int i = 1; i <= n; ++i)
    {
        deg[i] /= 2;
        if (deg[i] > 0)
        {
            add_edge(S, i, deg[i]);
            sum += deg[i];
        }
        else if (deg[i] < 0)
            add_edge(i, T, -deg[i]);
    }
    maxflow();
    for (int i = 1; i <= m; ++i)
        if (id[i] >= 0 && edge[id[i]].flow == edge[id[i]].cap)
            g[e[i].u].push_back(make_pair(e[i].v, i));
        else
            g[e[i].v].push_back(make_pair(e[i].u, i));
    print(1);
    putchar('\n');
}
signed main()
{
    freopen("euler.in", "r", stdin);
    freopen("euler.out", "w", stdout);
    read(n, m);
    S = 0, T = n + 1;
    int minn = 0, maxx = 0;
    for (int i = 1; i <= m; ++i)
    {
        read(e[i].u, e[i].v, e[i].w1, e[i].w2);
        if (e[i].w1 > e[i].w2)
        {
            swap(e[i].u, e[i].v);
            swap(e[i].w1, e[i].w2);
        }
        ++deg[e[i].u];
        ++deg[e[i].v];
        ckmax(minn, min(e[i].w1, e[i].w2));
        ckmax(maxx, max(e[i].w1, e[i].w2));
    }
    for (int i = 1; i <= n; ++i)
        if (deg[i] & 1)
        {
            printf("NIE\n");
            return 0;
        }
    memset(deg, 0, sizeof(deg));
    int l = minn, r = maxx, ans = 0;
    while (l <= r)
    {
        int mid = (l + r) >> 1;
        if (check(mid))
        {
            ans = mid;
            r = mid - 1;
        }
        else
            l = mid + 1;
    }
    printf("%d\n", ans);
    solve(ans);
    return 0;
}

#474. 「决策单调性优化 DP」网格选点

考场上思考如何对每一层求出单点所练成的最小的矩形。

赛后发现dp具有决策单调性,具体证明过程不在此赘述,可以形象理解,考虑当\(\delta x\)增大时其决策的位置一定会往后移动,因为此时\(\delta y\)所产生的贡献影响增大,又因为随着\(x\)增大其\(y\)在减小。

考虑如何处点坐标的偏序关系,可以使用线段树分治即可。

「决策单调性优化 DP」网格选点
// code by fhq_treap
#include <bits/stdc++.h>
#define ll long long
#define N 1000005

inline ll read() {
    char C = getchar();
    ll A = 0, F = 1;
    while (('0' > C || C > '9') && (C != '-')) C = getchar();
    if (C == '-')
        F = -1, C = getchar();
    while ('0' <= C && C <= '9') A = (A << 1) + (A << 3) + (C - 48), C = getchar();
    return A * F;
}

template <typename T>
void write(T x) {
    if (x < 0) {
        putchar('-');
        x = -x;
    }
    if (x > 9)
        write(x / 10);
    putchar(x % 10 + '0');
    return;
}

int n;

struct Point {
    int x, y, ans;
} P[N];

bool operator<(Point a, Point b) { return a.x < b.x; }

int T[N];  // BIT

#define MAXN 1000000
#define lowbit(x) (x & -x)

inline void add(int x, int p) {
    x = std::max(1, x);
    for (int i = x; i <= N; i += lowbit(i)) T[i] = std::max(T[i], p);
}

inline int find(int x) {
	if(x == 0)
	return 1;
    if (x <= 0)
        return 0;
    int ans = 0;
    for (int i = x; i; i -= lowbit(i)) {
        ans = std::max(ans, T[i]);
    }
    return ans;
}

int t;

using std::vector;

vector<int> v[N];  //第i层。

vector<int> need[N];  //处理的点

inline bool in(int li, int ri) {  //? li \to ri
    return (P[li].x < P[ri].x && P[li].y < P[ri].y);
}

inline ll S(int a, int b) {
//    std::cout<<"SSS "<<P[a].x<<" "<<P[a].y<<" "<<P[b].x<<" "<<P[b].y<<" "<<1ll * (P[a].x - P[b].x) * (P[a].y - P[b].y)<<std::endl;
    return 1ll * (P[a].x - P[b].x) * (P[a].y - P[b].y);
}

#define mid ((l + r) >> 1)
#define ls(x) (x << 1)
#define rs(x) (x << 1 | 1)

inline void cover(int u, int l, int r, int w, int d) {  //要覆盖的点是w,层数是d
//	std::cout<<u<<" "<<l<<" "<<r<<" "<<w<<" "<<d<<std::endl;
    if (P[v[d - 1][l]].x > P[w].x || P[v[d - 1][r]].y > P[w].y)
        return;
    if (in(v[d - 1][l],w) && in(v[d - 1][r], w)) {
//        std::cout<<u<<" "<<l<<" "<<r<<" "<<mid<<" "<<w<<" "<<d<<std::endl;
        return void(need[u].push_back(w));
    }
    if (l == r)
        return;
    cover(ls(u), l, mid, w, d);
    cover(rs(u), mid + 1, r, w, d);
}

#define inf 1e18

ll f[N];

inline void solve(int u, int li, int ri, int l, int r, int d) {  //上一层是[li,ri],对[l,r]做分治的贡献
//    	std::cout<<"扶桑大红花丶"<<u<<" "<<li<<" "<<ri<<" "<<l<<" "<<r<<" "<<d<<std::endl;
    if (l > r)
        return;
    int w = 0;  //中点转移的位置;
    ll val = inf;
//    	std::cout<<"INTO FIND W"<<std::endl;
//    	std::cout<<v[1][0]<<" "<<v[d - 1][li]<<" "<<P[v[d - 1][li]].x<<" "<<P[v[d - 1][li]].y<<" "<<P[need[u][mid]].x<<" "<<P[need[u][mid]].y<<std::endl;
    for (int i = li; i <= ri; ++i) {
        if (f[v[d - 1][i]] + 1ll * S(need[u][mid], v[d - 1][i]) < val)
            val = f[v[d - 1][i]] + 1ll * S(need[u][mid], v[d - 1][i]), w = i;
    }
    f[need[u][mid]] = std::min(f[need[u][mid]], val);
//    	std::cout<<"FIND "<<w<<" "<<val<<std::endl;
    solve(u, w, ri, l, mid - 1, d);
    solve(u, li, w, mid + 1, r, d);
}

inline void dfs(int u, int l, int r, int d) {
//    	std::cout<<"分治处理"<<std::endl;
//    	std::cout<<u<<" "<<l<<" "<<r<<" "<<d<<std::endl;
    solve(u, l, r, 0, (int)need[u].size() - 1, d);
    need[u].clear();
    if (l == r)
    return;
    dfs(ls(u), l, mid, d);
    dfs(rs(u), mid + 1, r, d);
}

signed main() {
    freopen("grid.in", "r", stdin);
    freopen("grid.out", "w", stdout);
    scanf("%d%d", &n, &t);
    for (int i = 1; i <= n; ++i) {
        scanf("%d%d", &P[i].x, &P[i].y);
    }
    P[++n].x = 0, P[n].y = 0;
    P[++n].x = t, P[n].y = t;
    std::sort(P + 1, P + n + 1);
    for (int i = 1; i <= n; ++i) {
        P[i].ans = find(P[i].y - 1) + 1;
//    	std::cout<<P[i].x<<" "<<P[i].y<<" "<<P[i].ans<<std::endl;
        add(P[i].y, P[i].ans);
    }  //
    int t = 0;  //层数
    for (int i = 1; i <= n; ++i) {
        v[P[i].ans].push_back(i);
        t = std::max(P[i].ans, t);
        f[i] = (P[i].ans == 1 ? 0 : inf);  //即答案
    }
    for (int i = 2; i <= t; ++i) {  //处理每一层
//    	puts("LAS");
//    	for(int j = 0;j < v[i - 1].size();++j)
//    	std::cout<<v[i - 1][j]<<" "<<P[v[i - 1][j]].x<<" "<<P[v[i - 1][j]].y<<" "<<P[v[i -1][j]].ans<<std::endl;
//		puts("DEL");
        for (int j = 0; j < (int)v[i].size(); ++j) {
//            std::cout<<"FUCK "<<P[v[i][j]].x<<" "<<P[v[i][j]].y<<" "<<P[v[i][j]].ans<<std::endl;
            cover(1, 0, (int)v[i - 1].size() - 1, v[i][j], i);
        }
        dfs(1, 0, v[i - 1].size() - 1, i);
//        for(int j = 0;j < (int)v[i].size();++j){
////        	std::cout<<"DO "<<P[v[i][j]].x<<" "<<P[v[i][j]].y<<" "<<P[v[i][j]].ans<<" "<<f[v[i][j]]<<std::endl;
//        }
    }
    ll ans = inf;
    for (int i = 1; i <= n; ++i)
        if (P[i].ans == t)
            ans = std::min(ans, f[i]);
    std::cout << ans << std::endl;
}
/*
5 20
19 1
2 6
9 15
10 3
13 11
*/

#574. 「二分图匹配」孤立点集

考虑\(Dilworth\)定理,有偏序关系时,其的最长反链 = 最小不可重链覆盖。

考虑\(DAG\)上的偏序关系实际上是其祖先链的关系,因为我们只关心最后的偏序关系链上的可重性,所以原图上其实是最小可重链覆盖。

考虑最小可重链覆盖是经典题目,只要使用二分图匹配即可。

考虑每个点的方案数就强制删去其可到达和他这个点再跑一次即可。

「二分图匹配」孤立点集
// code by fhq_treap
#include<bits/stdc++.h>
#define ll long long
using namespace std;
#define ri register int
const int maxn=110;
bool del[maxn],e[maxn][maxn];
int mchx[maxn],mchy[maxn],n,vis[maxn];
bool dfs(int p,int t){
	if(del[p]||vis[p]==t)return false;
	vis[p]=t;
	for(ri i=1;i<=n;++i)
		if(!del[i]&&e[p][i]&&(!mchy[i]||dfs(mchy[i],t))){
			mchx[p]=i;
			mchy[i]=p;
			return true;
		}
	return false;
}
int cnt;
inline int calc(){
	memset(mchx,0,sizeof mchx);
	memset(mchy,0,sizeof mchy);
	ri ret=0;
	for(ri i=1;i<=n;++i)ret+=dfs(i,++cnt);
	return ret;
}
bool tagx[maxn],tagy[maxn];
void dfs(int k){
	tagy[k]=true;
	for(ri i=1;i<=n;++i)
		if(e[i][k]&&!tagx[i]){
			tagx[i]=true;
			dfs(mchx[i]);
		}
}
int ans,m;
int main(){
	freopen("isolated.in","r",stdin);
	freopen("isolated.out","w",stdout);
	scanf("%d%d",&n,&m);
	while(m--){
		ri x,y;
		scanf("%d%d",&x,&y);
		e[x][y]=true;
	}
	for(ri k=1;k<=n;++k)
		for(ri i=1;i<=n;++i)
			if(e[i][k])
				for(ri j=1;j<=n;++j)
					if(e[k][j])
						e[i][j]=true;
	ans=n-calc();
	printf("%d\n",ans);
	for(ri i=1;i<=n;++i)
		if(!mchy[i])
			dfs(i);
	for(ri i=1;i<=n;++i)putchar(tagx[i]^tagy[i]|48);
	putchar(10);
	for(ri i=1;i<=n;++i){
		ri sum=n;
		for(ri j=1;j<=n;++j)del[j]=(i==j||e[i][j]||e[j][i]),sum-=del[j];
		putchar((sum-calc()==ans-1)|48);
	}
	return 0;
}

#976. 「母函数」随机减法

考虑一层的答案相当于\(E[now] - E[las]\),发现其贡献具有可减性后,那我们直接跳过中间层计算整体的答案。

考虑如何计算\(k\)轮过后的所有数期望乘积。

不妨先写出柿子。

\(E = \frac{1}{n^k}\sum_{\sum b_i = k}\frac{k!}{\prod b_i !}\prod (a_i - b_i)\)

哇,我们一看,几把柿子不做了。

考虑拆开贡献。

\(E = \frac{k!}{n^k}\sum_{\sum {b_i = k}}\prod\frac{a_i - b_i}{b_i!}\)

然后一看,哇可以卷积。

\(k = 1e9\).

考虑利用卷积的形式啊,\(sum\)的限制可以使用卷积解决其,前面的系数是平凡的。

考虑如何处理后面这个。
考虑使用生成函数。
\(f_i(x) = (a_i - x)e^x\)

那么就是\(F(x) = \prod_{i = 1}^n f_i e^{nx}= \sum_{i = 0}^n (a_i - x)\)

\(G(x) = \prod_{i = 1} ^ n (a_i - x) = \sum_{i = 0}^n c_i x ^ i\)

\([x^k]F(x) = \sum c_i \frac{n^{k - i}}{(k - i)!}\)

直接\(O(n^2)\)就可以解决了。

#976. 「母函数」随机减法
#include<cstdio>
#define ll long long
#define mod 1000000007
int n,k,e;
ll t,ans=0,res,inv,mul=1;
ll a[5002],c[5002]={};
inline int min(int x,int y)
{
	return x<y? x:y;
}

inline ll qpow(ll a,ll b){
	ll ans = 1;
	while(b){
		if(b & 1) ans = ans * a % mod;
		a = a * a % mod;
		b >>= 1;
	}
	return ans;
}
int main()
{
	freopen("calculate.in","r",stdin);
	freopen("calculate.out","w",stdout);
	scanf("%d%d",&n,&k),e=min(n,k),t=inv=qpow(n,mod-2),c[n]=1;
	for(int i=1;i<=n;++i)
	{
		scanf("%lld",&a[i]);
		for(int j=n-i;j<n;++j)c[j]=(c[j+1]*a[i]-c[j])%mod;
		c[n]=-c[n];
	}
	for(int i=1;i<=e;++i,t=(t*inv)%mod)
	{
		res=t;
		for(int j=k-i+1;j<=k;++j)res=(res*j)%mod;
		ans=(ans-c[i]*res)%mod;
	}
	printf("%lld",(ans+mod)%mod);
	return 0;
}



#594. 「费用流」大图书馆

考虑如何同时取到\(k\)和图书的限制。

我们发现其真是非常的困难。

我们不如真难则反,思考一下如何操作,我们强制每个点都在买了一次就扔掉,然后考虑是否保留这本书。

那么就可以用费用流刻画了。

考虑\(k\)减一,每个点的剩余流量表示其为还能为其他书保留多少空间。

那么转成了经典的区间覆盖最大权值点度有限的问题,构图不再赘述。

#594. 「费用流」大图书馆
#include<bits/stdc++.h>
using namespace std;
const int N=2009,inf=0x3f3f3f3f;
#define ll long long
typedef pair<int,ll>pii;

struct Edge{int to,nxt,c,w;}e[N*2]; int hd[N],tot=1;
void add(int u,int v,int c,int w){e[++tot]=(Edge){v,hd[u],c,w};hd[u]=tot;}
void addh(int u,int v,int c,int w){
//std::cout<<u<<" "<<v<<" "<<c<<" "<<w<<std::endl;
	add(u,v,c,w),add(v,u,0,-w);
}

int n,k,s,t,mflow,tmp;
ll cost;
ll d[N]; bool in[N];
bool spfa(){
	queue<int>q; q.push(s); memset(d,0,sizeof(d)); d[s]=1;
	while(!q.empty()) {
		int u=q.front(); q.pop(); in[u]=0;
		for(int i=hd[u],v;i;i=e[i].nxt)
			if(e[i].c&&d[v=e[i].to]<d[u]+e[i].w) {
				d[v]=d[u]+e[i].w;
				if(!in[v]) q.push(v),in[v]=1;
			}
	}
	return d[t]>0;
}
int dinic(int u,int flow) {
	int rest=flow; if(u==t) return flow; in[u]=1;
	for(int i=hd[u],v;i&&rest;i=e[i].nxt)
		if(!in[v=e[i].to]&&e[i].c&&d[v]==d[u]+e[i].w) {
			int used=dinic(v,min(e[i].c,rest));
			if(!used) d[v]=-1;
			rest-=used, e[i].c-=used, e[i^1].c+=used, cost+=used*e[i].w;
		}
	in[u]=0;
	return flow-rest;
}
pii flow(int ret=0,int tmp=0) {
	while(spfa()) while(tmp=dinic(s,inf)) ret+=tmp;
	return make_pair(ret,cost);
}
//上面为最大费用最大流MCMF模板

int las[N];
int a[N],c[N];

ll ans = 0;
ll del = 0;
ll sum = 0;

int main() {
	freopen("bibliotheca.in","r",stdin);
	freopen("bibliotheca.out","w",stdout);
	scanf("%d%d",&n,&k);
	for(int i = 1;i <= n;++i)
	scanf("%d",&a[i]);
	for(int i = 1;i <= n;++i)
	scanf("%d",&c[i]);
	for(int i = 1;i <= n;++i){
		if(las[a[i]] && las[a[i]] != i - 1)
		addh(las[a[i]] + 1,i,1,c[a[i]]);
		if(las[a[i]] && las[a[i]] == i - 1)
		del += c[a[i]];
		sum += c[a[i]];
		las[a[i]] = i;
	}
	k -= 1;
	s=n+1, t=n+2;
	for(int i=1;i<n;i++) addh(i,i+1,k,0);
	addh(s,1,k,0), addh(n,t,k,0);
//	std::cout<<flow().second<<std::endl;
//	std::cout<<del<<std::endl;
	std::cout<<sum - del - flow().second<<std::endl;
}

#794. 「CDQ 分治 & 整体二分」奇度边集

考虑可以构造出来的条件为所有的连通块点数为偶数。

然后就是最小瓶颈树的过程。

然后可以线段树分治了。

「CDQ 分治 & 整体二分」奇度边集
#include <bits/stdc++.h>
#define MAXN 300005
int n,m;
int ans[MAXN];

struct Edges{
    int u,v,w,id;
} p[MAXN],q[MAXN];

#define pii std::pair<int,int>

struct DSU{
    int num,top;
    int fa[MAXN],size[MAXN];
    pii stk[MAXN];

    void init(int n) {num = n; for(int i = 1;i <= n;i++) fa[i] = i, size[i] = 1;}
    int find(int x) {return x == fa[x] ? x : find(fa[x]);}

    void merge(int x,int y){
        x = find(x); y = find(y);
        if(x == y) return;
        if(size[x] < size[y]) std::swap(x,y);
        num -= (size[x] & 1) + (size[y] & 1);
        fa[y] = x; size[x] += size[y]; num += (size[x] & 1);
        stk[++top] = std::make_pair(x,y);
    }

    void undo(){
        int x = stk[top].first, y = stk[top].second; top -= 1;
        num -= (size[x] & 1); size[x] -= size[y];
        fa[y] = y; num += (size[x] & 1) + (size[y] & 1);
    }
} dsu;

void solve(int l,int r,int x,int y){
    if(l > r) return;
    int mid = (l + r) >> 1, lst = dsu.top, ansmid = -1;
    for(int i = l;i <= mid;i++)
        if(q[i].id < x) dsu.merge(q[i].u,q[i].v);
    for(int i = x;i <= y;i++){
        if(p[i].id <= mid) dsu.merge(p[i].u,p[i].v);
        if(dsu.num == 0) {ansmid = i; break;}
    }
    while(dsu.top > lst) dsu.undo();
    if(ansmid == -1){
        for(int i = l;i <= mid;i++) ans[i] = -1;
        for(int i = l;i <= mid;i++)
            if(q[i].id < x) dsu.merge(q[i].u,q[i].v);
        solve(mid + 1,r,x,y);
        while(dsu.top > lst) dsu.undo(); return;
    }
    ans[mid] = p[ansmid].w;
    for(int i = l;i <= mid;i++)
        if(q[i].id < x) dsu.merge(q[i].u,q[i].v);
    solve(mid + 1,r,x,ansmid); while(dsu.top > lst) dsu.undo();
    for(int i = x;i <= ansmid;i++)
        if(p[i].id < l) dsu.merge(p[i].u,p[i].v);
    solve(l,mid - 1,ansmid,y); while(dsu.top > lst) dsu.undo();
}

bool cmp(const Edges &x,const Edges &y) {return x.w < y.w;}

int main(){
    freopen("edges.in", "r", stdin);
    freopen("edges.out", "w", stdout);
    scanf("%d%d",&n,&m); dsu.init(n);
    for(int i = 1;i <= m;i++){
        scanf("%d%d%d",&q[i].u,&q[i].v,&q[i].w);
        p[i] = q[i]; p[i].id = i;
    }
    std::sort(p + 1,p + 1 + m,cmp);
    for(int i = 1;i <= m;i++) q[p[i].id].id = i;
    solve(1,m,1,m);
    for(int i = 1;i <= m;i++) printf("%d\n",ans[i]);
    return 0;
}

#915. 「欧拉函数」欧拉欧拉

考虑\(max - min\)容斥可以转成\(lcm - gcd\)容斥

那么发现其套上一个\(phi\)也是一样的。

那么可以写出柿子:

\(lcm(S) = \phi_{T \in S} gcd(T) ^ {(-1) ^ {|T| - 1}}\)

于是答案变成\(\prod_{w = 1}^k \prod _{i1 = 1} ^ n \prod_{i2 = 1}^n...\prod_{iw = 1}(\phi(gcd(i1,i2,...,iw)))^{(-1)^{w - 1}\binom{k}{w}n^{k - w}}\)

可以使用莫比乌斯反演即可。

#634. 「左偏树」转移石子

考虑其可以使用费用流模型操作,因为费用流的复杂度不对,于是我们自然的想到了使用了模拟费用流。

考虑在树上操作,不如枚举路径的两端点的LCA,考虑把接受点和输出点分别开两个堆,则跑一次流量的的答案为\(d_x + d_y - 2*d_z - inf\),我们枚举\(z\)是固定的,把输出端\(d_x -inf\)在一个堆里,输入段在\(d_y\)在一个堆里,如果要退游时则考虑把\(2*d_z - B_y\)丢入输入堆表示可以换输出端,\(2d_z -A_x\)丢入输出端表示可以换输入堆。

于是可以在树上写启发式合并优先队列\(O(nlog^2n)\)

然后就光荣被卡常数退役了。

于是使用平板电视里自带的可合并堆就行了。

#634. 「左偏树」转移石子
#include <bits/stdc++.h>
#include <ext/pb_ds/priority_queue.hpp>
#define file(x) freopen(#x".in","r",stdin); freopen(#x".out","w",stdout)
#define mp make_pair
using namespace std;
typedef long long ll;

int read() {
    int X = 0, w = 1;
    char c = getchar();

    while (c < '0' || c > '9') {
        if (c == '-')
            w = -1;

        c = getchar();
    }

    while (c >= '0' && c <= '9')
        X = X * 10 + c - '0', c = getchar();

    return X * w;
}

const int N = 250000 + 10;
const ll inf = 1e12;

int n, x[N], y[N];
ll ans = 0;
vector<pair<int, int>> E[N];
struct node {
    ll cost;
    mutable int cnt;
};
bool operator <(node x, node y) {
    return x.cost < y.cost;
}
bool operator >(node x, node y) {
    return x.cost > y.cost;
}
__gnu_pbds::priority_queue<node, greater<node>> M[N], H[N];

void dfs(int u, int fa, ll dep) {
    H[u].push((node) {
        dep, x[u]
    }), M[u].push((node) {
        dep - inf, y[u]
    });

    for (auto t : E[u]) {
        int v = t.first, w = t.second;

        if (v == fa)
            continue;

        dfs(v, u, dep + w);
        H[u].join(H[v]), M[u].join(M[v]);
    }

    while (!M[u].empty() && !H[u].empty()) {
        auto m = M[u].top(), h = H[u].top();
        ll cost = m.cost + h.cost - 2 * dep;
        int f = min(m.cnt, h.cnt);

        if (cost >= 0)
            break;

        ans += cost * f;
        M[u].top().cnt -= f;

        if (!M[u].top().cnt)
            M[u].pop();

        H[u].top().cnt -= f;

        if (!H[u].top().cnt)
            H[u].pop();

        M[u].push((node) {
            -cost + m.cost, f
            });
        H[u].push((node) {
            -cost + h.cost, f
            });
    }
}

int main() {
	file(rock);
    n = read();
    int s = 0;

    for (int i = 1; i < n; ++i) {
        int u = read(), v = read(), w = read();
        E[u].emplace_back(mp(v, w)), E[v].emplace_back(mp(u, w));
    }

    for (int i = 1; i <= n; ++i)
        x[i] = read(), y[i] = read(), s += y[i];

    dfs(1, 0, 0);
    printf("%lld\n", ans + 1ll * s * inf);
    return 0;
}

#904. 「拉格朗日插值」网格序列

考虑判断结论行列的数字只要最大的数字是一样就可以构造出答案。

那么答案实际上为\(\sum_{i = 1}^k i^{n + m} - i^n(i - 1)^m - (i - 1)^ni^m + (i-1)^{n + m}\)

其为一个\(n + m + 1\)项的多项式,所以可以使用拉格朗日插值考虑。

考虑拉格朗日的公式\(f(k) = \sum_{i = 1}^c y_i \prod_{i != j} \frac{k - x_i}{x_i - x_j}\)

不妨我们只处理出\(0~n + m\)的点值,此时\(x_i = i\)

那么知道\((x) = \sum y_i \prod_{i != j}\frac{x - i}{i - j}\)

那么知后面这个分数是很平凡的,那我们就可以\(O(n + m)\)插值出这多项式。

#904. 「拉格朗日插值」网格序列
#include <bits/stdc++.h>

#define MOD 998244353
#define ll long long

using namespace std;

ll pow_mod(ll x, int k) {
    ll result = 1;
    while (k) {
        if (k & 1)
            result = result * x % MOD;
        x = x * x % MOD;
        k >>= 1;
    }
    return result;
}

ll facd[2000005], facv[2000005];

void pre(int n) {
    facd[0] = 1;
    for (int i = 1; i <= n; i++) facd[i] = facd[i - 1] * i % MOD;
    facv[n] = pow_mod(facd[n], MOD - 2);
    for (int i = n - 1; i >= 0; i--) facv[i] = facv[i + 1] * (i + 1) % MOD;
}

ll f[2000005];

ll lagrange(int n, int k) {
    static ll l[2000005], r[2000005];
    if (k <= n + 1)
        return f[k];
    l[0] = r[n + 2] = 1;
    for (int i = 1; i <= n + 1; i++) l[i] = l[i - 1] * (k - i) % MOD;
    for (int i = n + 1; i > 0; i--) r[i] = r[i + 1] * (k - i) % MOD;
    ll result = 0;
    for (int i = 1; i <= n + 1; i++)
        result = (result + facv[i - 1] * facv[n - i + 1] % MOD * (((n - i) & 1) ? 1 : MOD - 1) % MOD *
                               l[i - 1] % MOD * r[i + 1] % MOD * f[i]) %
                 MOD;
    return result;
}

int main() {
    freopen("grid.in", "r", stdin);
    freopen("grid.out", "w", stdout);
    int T = 1;
    while (T--) {
        int n, m, k;
        scanf("%d%d%d", &n, &m, &k);
        pre(n + m);
        for (int i = 1; i <= n + m; i++)
            f[i] = (f[i - 1] +
                    (pow_mod(i, n) - pow_mod(i - 1, n) + MOD) * (pow_mod(i, m) - pow_mod(i - 1, m) + MOD)) %
                   MOD;
        printf("%lld\n", lagrange(n + m - 1, k));
    }
    return 0;
}

#764. 「启发式合并」交换游戏

考虑在A上做启发式合并,在\(B\)上做启发式合并,发现如果考虑把\((u,v)\)的话,\(B\)选的一定是在\(u \to lca,v\to lca\)的路径上的,且两点在\(A\)中应该是分属子树两边。

#764. 「启发式合并」交换游戏
#include<bits/stdc++.h>
#define pb push_back
using namespace std;

const int N=2e5+10,M=N*60;

namespace IO
{
	int read()
	{
		int ret=0;char c=getchar();
		while(!isdigit(c)) c=getchar();
		while(isdigit(c)) ret=ret*10+(c^48),c=getchar();
		return ret;
	}
	void write(int x){if(x>9)write(x/10);putchar(x%10^48);}
	void writesp(int x){write(x);putchar(' ');}
}
using namespace IO;

namespace Segment
{
	int rt[N];
	struct tr
	{
		int sz,sum[M],ls[M],rs[M];
		void update(int &x,int l,int r,int p,int v)
		{
			if(!x) x=++sz;sum[x]+=v;
			if(l==r) return;
			int mid=(l+r)>>1;
			if(p<=mid) update(ls[x],l,mid,p,v);
			else update(rs[x],mid+1,r,p,v);
		}
		int query(int x,int l,int r,int L,int R)
		{
			//printf("Q:%d %d %d %d %d\n",x,l,r,L,R);
			if(L>R || !x) return 0;
			if(L<=l && r<=R) return sum[x];
			int mid=(l+r)>>1,res=0;
			if(L<=mid) res+=query(ls[x],l,mid,L,R);
			if(R>mid) res+=query(rs[x],mid+1,r,L,R);
			return res;
		}
		int merge(int x,int y,int l,int r)
		{
			if(!x || !y) return x+y;
			int mid=(l+r)>>1,z=++sz;
			sum[z]=sum[x]+sum[y];
			if(l^r)	ls[z]=merge(ls[x],ls[y],l,mid),rs[z]=merge(rs[x],rs[y],mid+1,r);
			return z;
		}
		void clear()
		{
			for(int i=0;i<=sz;++i) sum[i]=ls[i]=rs[i]=0;
			sz=0;
		}
		void print(int x,int l,int r)
		{
			printf("%d %d %d %d\n",x,l,r,sum[x]);
			if(l==r) return;
			int mid=(l+r)>>1;
			print(ls[x],l,mid);print(rs[x],mid+1,r);
		}
	}tr;
}
using namespace Segment;

namespace Tree
{
	struct Tway{int v,nex,id;};
	struct Tree
	{
		int tot,ind;
		int head[N],top[N],son[N],fa[N],siz[N],pos[N],dep[N];
		Tway e[N<<1];
		void add(int u,int v,int id)
		{
			e[++tot]=(Tway){v,head[u],id};head[u]=tot;
			e[++tot]=(Tway){u,head[v],id};head[v]=tot;
		}
		void dfs1(int x)
		{
			siz[x]=1;
			for(int i=head[x];i;i=e[i].nex)
			{
				int v=e[i].v;
				if(v==fa[x]) continue;
				fa[v]=x;dep[v]=dep[x]+1;dfs1(v);siz[x]+=siz[v];
				if(siz[v]>siz[son[x]]) son[x]=v;
			}
		}
		void dfs2(int x,int tp)
		{
			top[x]=tp;pos[x]=++ind;
			if(son[x]) dfs2(son[x],tp);
			for(int i=head[x];i;i=e[i].nex)
			{
				int v=e[i].v;
				if(v==fa[x] || v==son[x]) continue;
				dfs2(v,v);
			}
		}
		void build(){dfs1(1);dfs2(1,1);}
		int lca(int x,int y)
		{
			while(top[x]^top[y])
			{
				if(dep[top[x]]>=dep[top[y]])x=fa[top[x]];
				else y=fa[top[y]];
			}
			return dep[x]<dep[y]?x:y;
		}
		void clear()
		{
			for(int i=0;i<=ind;++i) head[i]=top[i]=dep[i]=fa[i]=siz[i]=son[i]=pos[i]=0;
			tot=ind=0;
		}
	}T1,T2;
}
using namespace Tree;

namespace DreamLolita
{
	int n,ans[N],fr[N];
	vector<int>tag[N];
	int querychain(int root,int x,int y)
	{
		//printf("query:%d %d\n",x,y);
		int res=0;
		while(T2.top[x]^T2.top[y])
		{
			if(T2.dep[T2.top[x]]<T2.dep[T2.top[y]]) swap(x,y);//should jump x
			res+=tr.query(root,1,n,T2.pos[T2.top[x]],T2.pos[x]);x=T2.fa[T2.top[x]];
		}
		if(T2.dep[x]<T2.dep[y]) swap(x,y);
		//printf("%d %d %d\n",root,T2.pos[y]+1,T2.pos[x]);
		res+=tr.query(root,1,n,T2.pos[y]+1,T2.pos[x]);//-1 because no lca
		return res;
	}
	void dfstag(int x)//put tag on T1,so dfs T2
	{
		for(int i=T2.head[x];i;i=T2.e[i].nex)
		{
			int v=T2.e[i].v,d=T2.e[i].id;
			if(v==T2.fa[x]) continue;
			tag[v].pb(d);tag[x].pb(d);tag[T1.lca(x,v)].pb(-d);
			fr[d]=v;dfstag(v);//point v maintain edge d on T2
		}
	}
	void dfs(int x,int d)//calc ans,so dfs T1,and add  on T2,query on T2,use Heavy_Light cut
	{
		for(int i=T1.head[x];i;i=T1.e[i].nex)//first dfs then calc
		{
			int v=T1.e[i].v;
			if(v==T1.fa[x]) continue;
			dfs(v,T1.e[i].id);rt[x]=tr.merge(rt[x],rt[v],1,n);
		}
		if(x==1) return;
		for(auto i:tag[x])//push tag,+1 or -2
		{
			if(i>0) tr.update(rt[x],1,n,T2.pos[fr[i]],1);
			else tr.update(rt[x],1,n,T2.pos[fr[-i]],-2);
		}
		//printf("now:%d\n",x);tr.print(rt[x],1,n);puts("");
		ans[d]=querychain(rt[x],x,T1.fa[x]);
	}
	void clear()
	{
		T1.clear();T2.clear();tr.clear();
		for(int i=0;i<=n;++i) tag[i].clear(),fr[i]=0,rt[i]=0;
	}
	void solution()
	{
		n=read();
		for(int i=1;i<n;++i) T1.add(read(),read(),i);
		for(int i=1;i<n;++i) T2.add(read(),read(),i);
		T1.build();T2.build();dfstag(1);dfs(1,0);
		for(int i=1;i<n;++i) writesp(ans[i]); puts("");
		clear();
	}
}


int main()
{
	freopen("exchange.in","r",stdin);
	freopen("exchange.out","w",stdout);
	DreamLolita::solution();
	return 0;
}

#704. 「树链剖分」树的核心

考虑求的是一个带修的带权重心问题,我们发现其\(x\)为根的1所在的子树等价于\(1\)为根的时,\(x\)为根的子树补,其大小小于\(\frac{1}{2}\),所以其\(x\)的子树一定大于\(\frac{1}{2}\),那么按\(dfn\)序,权值和的中点一定在\(x\)的子树里,二分找到他,然后考虑倍增找到深度最大的满足子树大小大于\(\frac{1}{2}\)的点。

#704. 「树链剖分」树的核心
#include <bits/stdc++.h>
using namespace std;
#define re register
#define ll long long
inline int read() {
    int f = 1, lzx = 0;
    char c = getchar();
    while (c > '9' || c < '0') {
        if (c == '-')
            f = -f;
        c = getchar();
    }
    while (c <= '9' && c >= '0') {
        lzx = lzx * 10 + c - '0';
        c = getchar();
    }
    return lzx * f;
}
const int N = 1e5 + 10;
int n, q, fa[N][20], head[N], cnt, to[N], from[N], sz[N], dep[N], top[N], dfn[N], rev[N], T, heavy[N];
ll sum[N * 4], tag[N * 4], all;
inline void link(int x, int y) {
    from[++cnt] = head[x];
    head[x] = cnt;
    to[cnt] = y;
    return;
}
inline void dfs1(int x) {
    dep[x] = dep[fa[x][0]] + 1;
    sz[x] = 1;
    for (re int i = head[x]; i; i = from[i]) {
        int v = to[i];
        dfs1(v);
        sz[x] += sz[v];
        if (sz[v] > sz[heavy[x]])
            heavy[x] = v;
    }
    return;
}
inline void dfs2(int x) {
    dfn[x] = ++T;
    rev[T] = x;
    if (!top[x])
        top[x] = x;
    if (heavy[x]) {
        top[heavy[x]] = top[x];
        dfs2(heavy[x]);
    }
    for (re int i = head[x]; i; i = from[i]) {
        int v = to[i];
        if (v == heavy[x])
            continue;
        dfs2(v);
    }
    return;
}
inline void pushdown(int l, int r, int k) {
    if (tag[k]) {
        int mid = l + r >> 1;
        sum[k << 1] += tag[k] * (mid - l + 1);
        sum[k << 1 | 1] += tag[k] * (r - mid);
        tag[k << 1] += tag[k];
        tag[k << 1 | 1] += tag[k];
        tag[k] = 0;
    }
    return;
}
inline void change(int l, int r, int x, int y, int k) {
    if (l > y || r < x)
        return;
    if (l >= x && r <= y) {
        sum[k] += r - l + 1;
        tag[k]++;
        return;
    }
    int mid = l + r >> 1;
    pushdown(l, r, k);
    change(l, mid, x, y, k << 1);
    change(mid + 1, r, x, y, k << 1 | 1);
    sum[k] = sum[k << 1] + sum[k << 1 | 1];
    return;
}
inline void add(int x, int y) {
    while (top[x] != top[y]) {
        if (dep[top[x]] < dep[top[y]])
            swap(x, y);
        change(1, n, dfn[top[x]], dfn[x], 1);
        x = fa[top[x]][0];
    }
    if (dep[x] > dep[y])
        swap(x, y);
    change(1, n, dfn[x], dfn[y], 1);
    return;
}
inline int getpos(int l, int r, int k, ll res) {
    if (l == r)
        return l;
    int mid = l + r >> 1;
    pushdown(l, r, k);
    if ((sum[k << 1] + res) * 2 >= all)
        return getpos(l, mid, k << 1, res);
    return getpos(mid + 1, r, k << 1 | 1, res + sum[k << 1]);
}
inline ll query(int l, int r, int x, int y, int k) {
    if (l >= x && r <= y)
        return sum[k];
    if (l > y || r < x)
        return 0;
    int mid = l + r >> 1;
    pushdown(l, r, k);
    return query(l, mid, x, y, k << 1) + query(mid + 1, r, x, y, k << 1 | 1);
}
int main() {
    freopen("core.in", "r", stdin);
    freopen("core.out", "w", stdout);
    n = read(), q = read();
    for (re int i = 1; i < n; i++) {
        fa[i + 1][0] = read();
        link(fa[i + 1][0], i + 1);
    }
    for (re int i = 1; i < 20; i++)
        for (re int j = 1; j <= n; j++) fa[j][i] = fa[fa[j][i - 1]][i - 1];
    dfs1(1);
    dfs2(1);
    while (q--) {
        int op = read();
        if (op == 2) {
            int u = read(), v = read();
            add(u, v);
        } else {
            int u = read();
            change(1, n, dfn[u], dfn[u] + sz[u] - 1, 1);
        }
        all = sum[1];
        int pos = getpos(1, n, 1, 0);
        pos = rev[pos];
        for (re int i = 19; i >= 0; i--) {
            if (!fa[pos][i])
                continue;
            if (query(1, n, dfn[fa[pos][i]], dfn[fa[pos][i]] + sz[fa[pos][i]] - 1, 1) * 2 <= all)
                pos = fa[pos][i];
        }
        if (query(1, n, dfn[pos], dfn[pos] + sz[pos] - 1, 1) * 2 <= all)
            printf("%d\n", fa[pos][0]);
        else
            std::cout << pos << "\n";
    }
    return 0;
}

#944. 「莫比乌斯反演」随机添数

考虑期望的典中典中典公式。

\(E(x) = \sum_{1 \leq i}P(i \leq x)\)

考虑答案实际上等价于\(i - 1\)\(gcd > 1\),那么考虑全局是很好求的,那么只要求\(gcd = 1\)的方案。

\(\sum_{1\leq a_i \leq m}[gcd(a_i) = 1]\)

考虑莫反一下则有\(\sum_{d = 1}^m\mu(d)\lfloor\frac{m}{d}\rfloor^{i - 1}\)

image

image

image

#944. 「莫比乌斯反演」随机添数
// code by fhq_treap
#include<bits/stdc++.h>
#define ll long long
#define N 300005
#define uint unsigned int

inline ll read(){
    char C=getchar();
    ll A=0 , F=1;
    while(('0' > C || C > '9') && (C != '-')) C=getchar();
    if(C == '-') F=-1 , C=getchar();
    while('0' <= C && C <= '9') A=(A << 1)+(A << 3)+(C - 48) , C=getchar();
    return A*F;
}

template <typename T>
void write(T x)
{
    if(x < 0) {
        putchar('-');
        x = -x;
    }
    if(x > 9)
        write(x/10);
    putchar(x % 10 + '0');
    return;
}

int n;
int mu[N];
int v[N];
int s[N];

#define mod 1000000007

int p[N],t;

inline void Sieve(){
	mu[1] = 1;
	for(int i = 2;i <= 1e5;++i){
		if(!v[i]){
			mu[i] = -1;
			p[++t] = i;
		}
		for(int j = 1;j <= t && p[j] * i <= 1e5;++j){
			v[p[j] * i] = 1;
			if(i % p[j] == 0){
				mu[i * p[j]] = 0;
				break;
			}else
				mu[i * p[j]] = - mu[i];
		}
	}
	for(int i = 1;i <= 1e5;++i)
	s[i] = (s[i - 1] + mu[i] + mod) % mod;
}

inline ll qpow(ll a,ll b){
	ll ans = 1;
	while(b){
		if(b & 1)ans = ans * a % mod;
		a = a * a % mod;
		b >>= 1;
	}
	return ans;
}

int T;
uint ans = 0;
uint inv[N];

int main(){
	freopen("random.in","r",stdin);
	freopen("random.out","w",stdout);
	scanf("%d",&T);
	Sieve();
	for(int i = 1;i < N;++i)
	inv[i] = qpow(i,mod - 2);
	while(T -- ){
		n = read();
		int l = 2,r;
		ans = 1;
		for(;l <= n;l = r + 1){
			r = n / (n / l);
			ans = (ans + (mod - 1) * (s[r] - s[l - 1] + mod) % mod * (n / l) % mod * inv[n - n / l] % mod);
			if(ans > mod)
			ans -= mod;
		}
		write(ans);
		puts("");
	}
}


#964. 「FFT」字符匹配

考虑对字符集的每个字符进行操作:

\(A_i\)表示为是否能够被当前字符范围覆盖,\(B_i\)表示第二个字符串是否是这位。

考虑\(F_i = \sum A_{i + k} * B_i\)\(F_i\)等于第二个字符串里的该字符数量则该字符可以被从\(i\)被匹配。

把所有字符答案交起来即可。

#964. 「FFT」字符匹配
#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
typedef long long ll;
typedef double ld;
inline int Max(int x, int y) { return x > y ? x : y; }
inline int Min(int x, int y) { return x < y ? x : y; }
const int N = 800010;
const ld pi = acos(-1.0);
struct cpx {
	ld x, y;
	cpx(ld xx = 0, ld yy = 0) { x = xx; y = yy; }
};
cpx operator + (cpx a, cpx b) { return cpx(a.x + b.x, a.y + b.y); }
cpx operator - (cpx a, cpx b) { return cpx(a.x - b.x, a.y - b.y); }
cpx operator * (cpx a, cpx b) { return cpx(a.x * b.x - a.y * b.y, a.x * b.y + a.y * b.x); }
cpx *getw(int n, int type) {
	static cpx w[N/2];
	w[0] = cpx(1, 0); w[1] = cpx(cos(2 * pi / n), sin(2 * pi / n) * type);
	for(int i = 2; i < n/2; ++i) w[i] = w[i-1] * w[1];
	return w;
}
int p[N];
void FFT(cpx *a, int n, int type) {
	for(int i = 0; i < n; ++i) if(i < p[i]) std::swap(a[i], a[p[i]]);
	for(int i = 1; i < n; i <<= 1) {
		cpx *w = getw(i << 1, type);
		for(int j = 0; j < n; j += i << 1) {
			cpx *b = a + j, *c = b + i;
			for(int k = 0; k < i; ++k) {
				cpx v = w[k] * c[k];
				c[k] = b[k] - v;
				b[k] = b[k] + v;
			}
		}
	}
	if(type == -1) for(int i = 0; i < n; ++i) a[i].x /= n;
}
void mul(int *a, int *b, int *c, int n, int m) {
	static cpx f[N], g[N];
	int len = 1, ct = 0;
	while(len <= n + m) len <<= 1, ++ct;
	for(int i = 0; i < len; ++i) p[i] = (p[i>>1]>>1) | ((i&1) << (ct-1));
	for(int i = 0; i < len; ++i) f[i] = g[i] = cpx(0, 0);
	for(int i = 0; i < n; ++i) f[i] = cpx(a[i], 0);
	for(int i = 0; i < m; ++i) g[i] = cpx(b[i], 0);
	FFT(f, len, 1);
	FFT(g, len, 1);
	for(int i = 0; i < len; ++i) f[i] = f[i] * g[i];
	FFT(f, len, -1);
	for(int i = 0; i < len; ++i) c[i] = (int)(f[i].x+0.5);
}
int n, m, k;
int id[300], c[10][N], A[10][N];
int s[N], t[N];
int f[N], g[N];
signed main() {
	freopen("string.in","r",stdin);
	freopen("string.out","w",stdout);
	scanf("%d%d%d", &n, &m, &k);
	for(int i = 0;i <= 9;++i)
	id[i] = i;
	for(int i = 0;i < n;++i)
	scanf("%1d",&s[i]);
	for(int i = 0;i < m;++i)
	scanf("%1d",&t[i]);
	for(int i = 0; i < n; ++i) {
		s[i] = id[(int)s[i]]; t[i] = id[(int)t[i]];
		++c[(int)s[i]][Max(i-k, 0)];
		--c[(int)s[i]][Min(i+k+1, n)];
	}
	for(int i = 1; i < n; ++i) for(int j = 0; j <= 9; ++j) c[j][i] += c[j][i-1];
	for(int o = 0; o <= 9; ++o) {
		for(int i = 0; i < n; ++i)
			f[i] = c[o][i] ? 0 : 1;
		for(int i = 0; i < m; ++i)
			g[i] = t[i] == o ? 1 : 0;
		std::reverse(g, g + m);
		mul(f, g, A[o], n, m);
	}
	int ans = 0;
	for(int i = m-1; i < n; ++i){
		bool k = 1;
		for(int j = 0;j <= 9;++j)
		k = k & !A[j][i];
		if(k)
		++ans;
	}
	printf("%d\n", ans);
	return 0;
}
posted @ 2022-02-09 15:46  fhq_treap  阅读(127)  评论(0编辑  收藏  举报