【多校训练】2021牛客多校3

本文详细介绍了算法竞赛中常见的问题类型,如树套树、二分图匹配、椭圆曲线等,并通过具体的题目实例解析了相应的解题思路。此外,还探讨了如何在有限时间内解决复杂问题,如动态规划、贪心策略和暴力枚举的优化。文章以代码示例展示了具体实现,并提出了高效的数据结构和算法解决方案。
摘要由CSDN通过智能技术生成

【前言】
比赛的时候I想了一个俩log的方法,结果最后过不了,遂放弃。后来发现其实维护的东西没有那么多…
这场题目有点阴间,感觉剩下的题除了I都不太可做,题解都两三页。
rk47,校3/9,日常被踩

A. Guess and lies

【题意】

Alice想一个 n n n以内的数Bob来猜,每次Bob可以询问是否大于等于某个数,Alice会回答yes或no,但是Alice在一轮里面可以说一次谎。Alice希望猜的次数尽可能多,Bob希望猜的次数尽可能少。同时,Alice第一次一定会回答yes。

现在对于 x ∈ [ 1 , n ] x\in[1,n] x[1,n],求出当Bob第一次猜 x x x时,猜几轮Bob能知道这个数。

【思路】

不会。
在这里插入图片描述
【参考代码】

#include<bits/stdc++.h>
using namespace std;

const int N=2005;
const int mo=10007;
int f[24][N][N];
int n,L[N];
int main(){
    scanf("%d",&n);
//  n=2000;
    for (int i=1;i<=22;i++) L[i]=min(n,1<<i);
    memset(f,-1,sizeof(f));
    f[0][0][1]=0;
    f[0][0][0]=1;
    f[0][1][0]=0;
    
    for (int i=1;i<=22;i++){
        for (int j=0;j<=n;j++)
            for (int k=0;k+j<=n;k++)
                if (f[i-1][j][k]>=0){
                    int p=min(f[i-1][j][k],n-j-k);
                    f[i][p][j+k]=max(f[i][p][j+k],f[i-1][k][j]);
                }
        for (int j=0;j<=n;j++)
            for (int k=0;k+j<=n;k++)
                if (f[i-1][j][k]>=0) f[i][j][k]=max(f[i][j][k],f[i-1][j][k]+(L[i-1]-k));
        for (int j=n;j>=1;j--)
            for (int k=0;k+j<=n;k++)
                f[i][j-1][k]=max(f[i][j-1][k],f[i][j][k]);
        for (int j=0;j<=n;j++)
            for (int k=0;k<=n;k++)
                if (f[i][j][k]>=0){
                    int p=min(f[i][j][k],n-k);
                    f[i][p][k]=max(f[i][p][k],j);
                }
        for (int j=n;j>=1;j--)
            for (int k=0;k+j<=n;k++)
                f[i][j-1][k]=max(f[i][j-1][k],f[i][j][k]);
        /*for (int j=0;j<=n;j++)
            for (int k=0;k+j<=n;k++)
                if (f[j][k][i]>=0&&i<=5)
                    cout<<f[j][k][i]<<' '<<j<<' '<<k<<' '<<i<<endl;*/
    }
    
    for (int i=1;i<=n;i++){
        int j=22;
        for (;j&&f[j-1][i-1][n-i+1]>=0;) --j;
        printf("%d ",j);
    }
//  cerr<<clock()<<endl;
} 
B. Black and White

【题意】

给定一个 n × m n\times m n×m的网格,每个位置有一个涂黑的花费,现在要把所有位置涂黑,同时如果任意两行两列的四个交点中有三个已经涂黑,那么剩下的那个可以免费涂,求最小花费。

n ≤ 5000 n\leq 5000 n5000

【思路】

对于一个位置 ( i , j ) (i,j) (i,j),如果该格子是黑色,连一条 A i A_i Ai B j B_j Bj的边,操作不改变连通性。全部染黑等价于初始联通,那么用Prim求最小生成树就行,复杂度 O ( n 2 ) O(n^2) O(n2)

或者,我们考虑一个暴力的贪心(也是我们比赛时的做法):每次选择花费最小的一个位置涂黑,然后扩展现在所有染黑的位置,这样复杂度是 O ( n 3 ) O(n^3) O(n3)的,瓶颈实际上在扩展的地方。

考虑优化这个过程,可以观察到一个性质:如果某一行的两个位置都已经涂黑,那么这两个位置对应的列的状态一定相同。那么事实上我们只需要用并查集维护这堆状态相同的列,每次并查集合并的时候用bitset或一下就行。

合并次数是 O ( n ) O(n) O(n)的,那么总时间复杂度 O ( n 2 ω + n 2 log ⁡ n ) O(\frac {n^2}{\omega}+n^2\log n) O(ωn2+n2logn),后面是堆取出每个数的复杂度,其实远远跑不到上界,因为只需要自己涂黑 2 n − 1 2n-1 2n1个就行。

【参考代码】

/*
 * @date:2021-07-24 13:31:52
 * @source:
*/
#include <bits/stdc++.h>

using namespace std;

typedef pair<int, int> pii;
typedef long long ll;
typedef pair<ll, ll> pll;
typedef vector<int> vi;
#define fir first
#define sec second
#define ALL(x) (x).begin(), (x).end()
#define SZ(x) (int)x.size()
#define For(i, x) for (int i = 0; i < (x); ++i)
#define Trav(i, x) for (auto & i : x)
#define pb push_back
template<class T, class G> bool chkMax(T &x, G y) {
    return y > x ? x = y, 1 : 0;
}
template<class T, class G> bool chkMin(T &x, G y) {
    return y < x ? x = y, 1 : 0;
}

const int MAXN = 5000 + 5;

int N, M;
ll a, b, c, d, p;
int A[MAXN][MAXN];
struct Node {
    int val, x, y;
    bool operator < (const Node &x) const {
        return val > x.val;
    }
};
priority_queue<Node> Pq;
bitset<MAXN> C[MAXN];
int Fa[MAXN];

int findFa(int x) {
    return Fa[x] == x ? x : Fa[x] = findFa(Fa[x]);
}

int merge(int x, int y) {
    x = findFa(x), y = findFa(y);
    if (x == y) return x;
    Fa[x] = y;
    C[y] |= C[x];
    return y;
}

int v[MAXN];

int main() {
    scanf("%d%d%lld%lld%lld%lld%lld", &N, &M, &a, &b, &c, &d, &p);
    for (int i = 1; i <= N; ++i) {
        for (int j = 1; j <= M; ++j) {
            a = (a * a * b + a * c + d) % p;
            A[i][j] = a;
            Pq.push({A[i][j], i, j});
        }
    }
    int cnt = 0;
    ll val = 0;
    for (int i = 1; i <= M; ++i) {
        Fa[i] = i;
    }
    while (!Pq.empty()) {
        auto x = Pq.top(); Pq.pop();
        int fa = findFa(x.y);
        if (C[fa][x.x]) continue;
        if (v[x.x]) {
            v[x.x] = merge(v[x.x], x.y);
        } else v[x.x] = x.y;
        C[findFa(x.y)][x.x] = 1;
        val += x.val;
        if (++cnt == N + M - 1) break;
    }
    printf("%lld\n", val);
    return 0;
}
C. Minimum Grid

【题意】

一个 n × n n\times n n×n的网格,其中 m m m个格子里需要填非负数,现在给出每行每列的最大值,求所有可行方案中,填的数字和最小的任意一种。

n ≤ 2000 , m ≤ 8 × 1 0 5 n\leq 2000,m\leq 8\times 10^5 n2000,m8×105

【思路】

我们从考虑从大往小填数,不难发现,实际上使答案减小的方式,是在行列最大值相同的地方填上这个最大值,因为这样不仅能使填的数最多,而且能使小的数有更多可行的填放位置(如果没有最大值相同的,则需要在任意一个能放的位置放下)。

那么实际上我们只需要从大到小枚举每个数字,对当前枚举到的数字行列匹配,跑一下二分图匹配,在匹配的位置填上这个数字。其余需要满足最大值的位置再找到可以放的位置即可,具体实现可以见代码。

或者,答案最后的形式实际上是行列限制和减去上面匹配的数,我们可以直接建图跑最大费用流即可,具体来说,还是行列连边,在相同限制的位置连一条流量为 1 1 1,费用为这个限制的边即可。

复杂度 O ( m n ) O(m\sqrt n) O(mn )

【参考代码】

二分图匹配

#include<bits/stdc++.h>
#define pb push_back
#define mkp make_pair
#define fi first
#define se second
using namespace std;

typedef double db;
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<int,ll> pil;
const int N=2005,M=1e6+10,inf=0x3f3f3f3f;

int n,m,K,S,T;
int rmx[N],cmx[N],mp[N][N],hv_r[N],hv_c[N];
vector<int>row[M],col[M];
ll ans;

int read()
{
    int ret=0,f=1;char c=getchar();
    while(!isdigit(c)) {if(c=='-')f=0;c=getchar();}
    while(isdigit(c)) ret=ret*10+(c^48),c=getchar();
    return f?ret:-ret;
}

int tot,head[N<<1],dis[N<<1];
vector<int>vec;
queue<int>q;
struct Tway
{
    int v,w,nex;
}e[M];

//最大流
void add(int u,int v)
{
    e[++tot]=(Tway){v,1,head[u]};head[u]=tot;
    e[++tot]=(Tway){u,0,head[v]};head[v]=tot;
}
bool bfs()
{
    for(auto x:vec) dis[x]=-1;
    while(!q.empty()) q.pop();

    q.push(S);dis[S]=0;
    while(!q.empty())
    {
        int x=q.front();q.pop();
        for(int i=head[x];i;i=e[i].nex)
        {
            int v=e[i].v;
            if(!~dis[v] && e[i].w) dis[v]=dis[x]+1,q.push(v);
        }
    }
    return ~dis[T];
}

int dfs(int x,int flow)
{
    if(x==T || !flow) return flow;
    int f,used=0;
    for(int i=head[x];i;i=e[i].nex)
    {
        int v=e[i].v;
        if(dis[v]!=dis[x]+1 || !(f=dfs(v,min(flow-used,e[i].w)))) continue;
        e[i].w-=f;e[i^1].w+=f;used+=f;
        if(used==flow) break;
    }
    return used;
}

void clear_mp(int k)
{
    tot=1;
    head[S]=head[T]=0;
    vec.clear();
    for(auto i:row[k]) vec.pb(i);
    for(auto i:col[k]) vec.pb(i+n);
    vec.pb(S);vec.pb(T);
}

int dinic()
{
    int ret=0;
    while(bfs()) ret+=dfs(S,inf);
    return ret;
}

signed main()
{
    n=read();m=read();K=read();
    for(int i=1;i<=n;++i)
    {
        int t=read();
        rmx[i]=t;row[t].pb(i);
    }
    for(int i=1;i<=n;++i)
    {
        int t=read();
        cmx[i]=t;col[t].pb(i);
    }
    for(int i=1;i<=m;++i) 
    {
        int x=read(),y=read();
        mp[x][y]=1;
    }
    S=0;T=2*n+2;
    for(int k=K;k;k--)
    {
        if(!row[k].size() && !col[k].size()) continue;
        if(row[k].size() && col[k].size())
        {
            clear_mp(k);
            for(auto i:row[k]) for(auto j:col[k])
                if(mp[i][j]) add(i,j+n);

            for(auto i:row[k]) add(S,i);
            for(auto j:col[k]) add(j+n,T);
            int tmp=dinic();
            //printf("%d %d\n",k,tmp);
            ans+=1ll*(row[k].size()+col[k].size()-tmp)*k;

            for(int i=head[S];i;i=e[i].nex)
            {
                if(!e[i].w)
                {
                    int x=e[i].v;
                    for(int j=head[x];j;j=e[j].nex)
                    {
                        if(!e[j].w) 
                        {
                            mp[i][e[j].v-n]=0;
                            hv_r[x]=hv_c[e[j].v-n]=1;
                            break;
                        }
                    }
                } 
            }
        }
        else ans+=1ll*(row[k].size()+col[k].size())*k;
        
        for(auto i:row[k])
        {
            if(hv_r[i]) continue;
            for(int j=1;j<=n;++j)
            {
                if(mp[i][j] && cmx[j]>=k) 
                {
                    mp[i][j]=0;
                    hv_r[i]=1;
                    break;
                }
            }
        }
        for(auto j:col[k])
        {
            if(hv_c[j]) continue;
            for(int i=1;i<=n;++i)
            {
                if(mp[i][j] && rmx[i]>=k)
                {
                    mp[i][j]=0;
                    hv_c[j]=1;
                    break;
                }
            }
        } 
    }
    printf("%lld\n",ans);
    return 0;
}
 

费用流

#include<algorithm>
#include<bitset>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<ctime>
#include<iostream>
#include<map>
#include<queue>
#include<set>
#include<stack>
#include<string>
#include<vector>
using namespace std;
namespace Solve
{
	typedef long long LL;
	const int MAXN=4010;
	const int MAXM=500005;
	const LL INF=1000000005;
	inline LL mymin(LL a,LL b){return a<b?a:b;}
	inline int oid(int p){return (p&1)?(p+1):(p-1);}
	struct edge
	{
		int to,next,cap,flow;LL cost;
		edge(int a=0,int b=0,int c=0,int d=0,LL e=0):
			to(a),next(b),cap(c),flow(d),cost(e){}
	}E[MAXM<<1];
	int first[MAXN],np;
	void add_edge(int u,int v,int c,LL w)
	{
		//printf("%d->%d   %d   %d\n",u,v,c,w);
		E[++np]=edge(v,first[u],c,0,w);
		first[u]=np;
		E[++np]=edge(u,first[v],0,0,-w);
		first[v]=np;
		return;
	}
	int S,T;
	int q[MAXN],front,rear;
	LL dist[MAXN]; int cur[MAXN];
	bool inq[MAXN];
	bool SPFA()
	{
		memset(inq,false,sizeof(inq));
		for(int i=0;i<=T;i++) dist[i]=-INF;
		front=rear=0;
		q[rear++]=S,inq[S]=true,dist[S]=0;
		if(rear==MAXN) rear=0;
		while(rear-front)
		{
			int i=q[front++];
			if(front==MAXN) front=0;
			inq[i]=false;
			for(int p=first[i];p;p=E[p].next)
			{
				int j=E[p].to;
				if(E[p].cap>E[p].flow && dist[j]<dist[i]+E[p].cost)
				{
					dist[j]=dist[i]+E[p].cost;
					if(!inq[j])
					{
						inq[j]=true;
						q[rear++]=j;
						if(rear==MAXN) rear=0;
					}
				}
			}
		}
		return dist[T]>-INF;
	}
	int DFS(int i,int f,LL &mincost)
	{
		if(i==T || f==0) return f;
		int aug=0;inq[i]=true;
		for(int p=cur[i];p;p=E[p].next)
		{
			cur[i]=E[p].next;
			int j=E[p].to;
			if(inq[j]) continue;
			if(E[p].cap>E[p].flow && dist[j]==dist[i]+E[p].cost)
			{
				int nxaug=DFS(j,mymin(f-aug,E[p].cap-E[p].flow),mincost);
				if(!nxaug) continue;
				aug+=nxaug;
				E[p].flow+=nxaug;
				E[oid(p)].flow-=nxaug;
				mincost+=nxaug*E[p].cost;
				if(aug==f) break;
			}
		}
		inq[i]=false;
		if(aug==0) dist[i]=-1;
		return aug;
	}
	LL dinic()
	{
		int maxflow=0;LL mincost=0;
		while(SPFA())
		{
			for(int i=0;i<=T;i++) cur[i]=first[i];
			memset(inq,false,sizeof(inq));
			maxflow+=DFS(S,INF,mincost);
		}
		return mincost;
	}
	int n,m,k,b[MAXN],c[MAXN];
	void work()
	{
		scanf("%d%d%d",&n,&m,&k);
		LL tot = 0;
		for(int i=1;i<=n;i++) scanf("%d",&b[i]);
		for(int i=1;i<=n;i++) scanf("%d",&c[i]);
		for(int i=1;i<=n;i++) tot+=(LL)b[i]+c[i];
		np = 0;memset(first,0,sizeof(first));
		S = (n<<1)+1, T = (n<<1)+2;
		for(int i=1;i<=m;i++)
		{
			int x,y;scanf("%d%d",&x,&y);
			if(b[x] == c[y]) add_edge(x,y+n,1,b[x]);
		}
		for(int i=1;i<=n;i++) add_edge(S,i,1,0);
		for(int i=1;i<=n;i++) add_edge(i+n,T,1,0);
		LL temp = dinic();
		cout<<tot - temp<<endl;
		return;
	}
}
int main()
{
	#ifdef LOCAL
	freopen("00test.in","r",stdin);
	freopen("00test.out","w",stdout);
	#endif
	
	Solve::work();
	
	return 0;
}
D. Count

【题意】

有一个 n ∗ n n*n nn的棋盘, 要求选出 m m m个点的方案数, 使得每一行至少一个, 每一列至少一个, 每一个对角线至少一个,每一个副对角线至少一个, 同时有 K K K个点不能选.

【思路】

由于同时考虑 "必选"的要求和"必不能选"的要求比较困难, 考虑容斥.

答案可以表示成 ∑ i = 0 K ( − 1 ) i ⋅ " 强 行 选 i 个 非 法 点 , 剩 下 的 点 随 意 " 的 答 案 ( 记 为 d p [ S ] ) \sum_{i=0}^K (-1)^i\cdot "强行选i个非法点,剩下的点随意"的答案(记为dp[S]) i=0K(1)i"i,"(dp[S])

于是二进制枚举钦定选哪些非法点.

当选择了非法点时, 其对应的行,列,对角线和副对角线(如果有的话)就相当于直接选了, 就解除了行\列的限制.

而对角线的限制比较特殊, 首先, 如果钦定的点里面有在对角线上的, 那相当于对角线的要求已经达成了. 而若没有, 则还需要容斥, 来算出答案.

f 0 , f 1 f0,f1 f0,f1分别表示 “主/副对角线上的点必须不能放的要求”,则
d p [ S ] = s o l v e ( S , f 0 = 0 , f 1 = 0 ) − s o l v e ( S , f 0 = 1 , f 1 = 0 ) − s o l v e ( S , f 0 = 0 , f 1 = 1 ) + s o l v e ( S , f 0 = 1 , f 1 = 1 ) dp[S]=solve(S,f0=0,f1=0)-solve(S,f0=1,f1=0)-solve(S,f0=0,f1=1)+solve(S,f0=1,f1=1) dp[S]=solve(S,f0=0,f1=0)solve(S,f0=1,f1=0)solve(S,f0=0,f1=1)+solve(S,f0=1,f1=1)
S S S​中的点有主对角线上的, 那就去掉第2,4项, 若有副对角线上的, 就去掉第3,4项. 若都有, 就只考虑第一项.

下面考虑如何计算 s o l v e ( s , f 0 , f 1 ) solve(s,f0,f1) solve(s,f0,f1),即: 有的行\列必须选, 有的行可选可不选, 有的对角线必须不选, 的方案数.

首先我们知道, 若有A行可以选, B行不可以选, 同时有X个位置因为对角线而被ban掉, 那么可选的点数为 A B − X AB-X ABX, 要从中选出m个点,其中有 ∣ s ∣ |s| s个是来自非法点, 方案数为 ( A B − X − ∣ s ∣ m − ∣ s ∣ ) \binom{AB-X-\mid s\mid}{m-\mid s\mid} (msABXs)

然后要求出, 得到A,B,X的方案数, 可以用三维背包的方法, 可以通过枚举i,表示当前执行到了 i , n − i + 1 i,n-i+1 i,ni+1行,列, 枚举第i行,n-i+1行,i列,n-i+1列选不选, 结合f0,f1的要求来递推, 这个地方也要容斥(不知道为啥).

然后返回 ∑ ( A B − X − ∣ s ∣ m − ∣ s ∣ ) g ( A , B , X ) \sum \binom{AB-X-\mid s\mid}{m-\mid s\mid} g(A,B,X) (msABXs)g(A,B,X).

【参考代码】

#include<bits/stdc++.h>
#define rep(i,a,b) for(int i=(a),i##ss=(b);i<=i##ss;i++)
#define dwn(i,a,b) for(int i=(a),i##ss=(b);i>=i##ss;i--)
#define deb(x) cerr<<(#x)<<":"<<(x)<<'\n'
#define pb push_back
#define fi first
#define se second
#define hvie '\n'
using namespace std;
typedef pair<int,int> pii;
typedef long long ll;
typedef unsigned long long ull;
typedef double db;
int yh(){
    int ret=0;bool f=0;char c=getchar();
    while(!isdigit(c)){if(c==EOF)return -1;if(c=='-')f=1;c=getchar();}
    while(isdigit(c))ret=(ret<<3)+(ret<<1)+(c^48),c=getchar();
    return f?-ret:ret;
}
const int mod=10007;
int g[35][35][70],h[35][35][70];
int n,k,m;
int C[1050][1050];
int bi[1<<12];
int add(int x,int y){return (x+y)%mod;}
void inc(int &x,int y){x=(x+y)%mod;}
int mul(int x,int y){return x*y%mod;}
bool vx[50],vy[50];
int x[105],y[105];
int solve(int f0,int f1,int ban){//ban diag/negdiag , numbers of k chosen
    int T=0;
    memset(g,0,sizeof(g));
    g[0][0][0]=1;
    for(int l=1,r=n;l<=r;l++,r--){
        memset(h,0,sizeof(h));
        // cout<<T<<hv ie;
        rep(i,0,T)rep(j,0,T)rep(k,0,(T<<1))if(g[i][j][k]){
            if(l<r){
                rep(x1,vx[l],1)rep(x2,vx[r],1)
                rep(y1,vy[l],1)rep(y2,vy[r],1){
                    int ni=i+x1+x2,nj=j+y1+y2;
                    int nk=k+(f0)*((int)(x1&&y1)+(int)(x2&&y2))+
                            (f1)*((int)(x1&&y2)+(int)(x2&&y1));
                    if(x1^x2^y1^y2) inc(h[ni][nj][nk],mod-g[i][j][k]);
                    else inc(h[ni][nj][nk],g[i][j][k]);
                }
            }
            else{
                rep(x,vx[l],1)rep(y,vy[l],1){
                    int ni=i+x,nj=j+y;
                    int nk=k+ (f0||f1)*(x&&y);
                    if(x^y) inc(h[ni][nj][nk],mod-g[i][j][k]);
                    else inc(h[ni][nj][nk],g[i][j][k]);
                }
            }
        }
        T+=1+(l<r);
        rep(i,0,T)rep(j,0,T)rep(k,0,(T<<1)){
            g[i][j][k]=h[i][j][k];
        }
    }   
    int ans=0;
    rep(i,0,T)rep(j,0,T)rep(k,0,(T<<1)){
        if(i*j-k>=ban){
            inc(ans,mul(C[i*j-k-ban][m-ban],g[i][j][k]));
        }
    }
    return ans;
}
int F(int S){
    memset(vx,0,sizeof(vx));
    memset(vy,0,sizeof(vy));
    bool f0(0),f1(0);
    rep(i,0,k-1)if(S&(1<<i)){
        vx[x[i]]=1;
        vy[y[i]]=1;
        f0|=(x[i]==y[i]);
        f1|=(x[i]+y[i]==n+1);
    }
    int ans=solve(0,0,bi[S]);
    if(!f0) inc(ans,mod-solve(1,0,bi[S]));
    if(!f1) inc(ans,mod-solve(0,1,bi[S]));
    if(!f0&&!f1) inc(ans,solve(1,1,bi[S]));
    return (ans%mod+mod)%mod;
}
int main(){
    n=yh(),k=yh(),m=yh();
    rep(i,0,1024){
        C[i][0]=1;
        rep(j,1,i) C[i][j]=add(C[i-1][j],C[i-1][j-1]);
        bi[i]=bi[i>>1]+(i&1);
    }
    rep(i,0,k-1) x[i]=yh(),y[i]=yh();
    int ans=0;
    rep(i,0,(1<<k)-1){
        //m-=bi[i];
        if(bi[i]&1) ans=(ans-F(i)+mod)%mod;
        else ans=(ans+F(i))%mod;
        //m+=bi[i];
    }
    cout<<ans<<hvie;
    return 0;
}

E. Math

【题意】

给定 n n n,问有多少对 ( x , y ) (x,y) (x,y),满足 x y + 1 ∣ x 2 + y 2 , 1 ≤ x ≤ y ≤ n xy+1|x^2+y^2,1\leq x\leq y\leq n xy+1x2+y2,1xyn

n ≤ 1 0 18 n\leq 10^{18} n1018 1 0 5 10^5 105组数据

【思路】

考虑 x 2 + y 2 = k ( x y + 1 ) x^2+y^2=k(xy+1) x2+y2=k(xy+1)的解 ( x , y ) (x,y) (x,y),由韦达定理可知, ( x , k x − y ) (x,kx-y) (x,kxy)也是一组解,且 x ( k x − y ) = ( y 2 − 1 ) x(kx-y)=(y^2-1) x(kxy)=(y21)

由于 k x − y kx-y kxy的绝对值更小且依然非负(代入可知),那么对于该 k k k,绝对值最小的一组正整数解满足 k x − y = 0 kx-y=0 kxy=0,这组解有 ( a , a 3 ) (a,a^3) (a,a3)的形式,然后其余解可以通过 ( x , y ) → ( x , k x − y ) (x,y)\rightarrow(x,kx-y) (x,y)(x,kxy)的递降得到,于是也可以反推回去。

然后又发现取 k = a 2 k=a^2 k=a2可以覆盖所有情况,比如 ( a 3 , a 5 − a ) (a^3,a^5-a) (a3,a5a)也是一组解,然后 ( a 5 − a , a 7 − 2 a 3 ) (a^5-a,a^7-2a^3) (a5a,a72a3)也是一组解。

然后处理出大概 50 50 50个多项式,询问的时候二分一下就行了。

复杂度 O ( n 1 3 log ⁡ 2 n + T log ⁡ 2 n ) O(n^{\frac 1 3}\log ^2n+T\log^2 n) O(n31log2n+Tlog2n)

题解没有处理多项式,而是直接根据反推预处理了答案,复杂度可以降到 O ( n 1 3 + T log ⁡ n ) O(n^{\frac 1 3}+T\log n) O(n31+Tlogn)

事实上,这个东西是椭圆曲线。

【参考代码】

赛时代码

#include<bits/stdc++.h>
#define int long long
#define rep(i,a,b) for(int i=(a),i##ss=(b);i<=i##ss;i++)
#define dwn(i,a,b) for(int i=(a),i##ss=(b);i>=i##ss;i--)
#define deb(x) cerr<<(#x)<<":"<<(x)<<'\n'
#define pb push_back
#define fi first
#define se second
#define hvie '\n'
using namespace std;
typedef pair<int,int> pii;
typedef long long ll;
typedef unsigned long long ull;
typedef double db;
int yh(){
    int ret=0;bool f=0;char c=getchar();
    while(!isdigit(c)){if(c==EOF)return -1;if(c=='-')f=1;c=getchar();}
    while(isdigit(c))ret=(ret<<3)+(ret<<1)+(c^48),c=getchar();
    return f?-ret:ret;
}
const int maxn=3e5+5;

ll ksm(ll x,int p){
    ll ans=1;
    while(p){
        if(p&1) ans=ans*x;
    }
    return ans;
}
struct poly{
    int a[100];
    poly(){memset(a,0,sizeof(a));}
    void init(int x=0,int y=0,int z=0,int w=0){
        a[0]=x;a[1]=y;a[2]=z;a[3]=w;
    }
    void print(){
        rep(i,0,15) cout<<a[i]<<" ";
        puts("");
    }
    __int128 eval(ll x){
        __int128 now=1,ret=0;
        rep(i,0,69){
            ret+=now*a[i];
            now=now*x;
        }
        return ret;
    }
};
poly operator<<(poly v,int b){
    poly u;
    rep(i,0,69-b) u.a[i+b]=v.a[i];
    return u;
}
poly operator+(poly a,poly b){
    poly c;
    rep(i,0,69) c.a[i]=a.a[i]+b.a[i];
    return c;
}
poly operator-(poly a,poly b){
    poly c;
    rep(i,0,69) c.a[i]=a.a[i]-b.a[i];
    return c;
}
poly a[100];
vector<ll>s[65];
// ll s1[1000006],s2[1000006],s3[1000006];
int t1,t2,t3;
signed main(){
    a[1].init(0,1);
    int top=0;
    rep(i,2,50){
        a[i]=(a[i-1]<<2)-a[i-2];
        if(a[i].eval(2)>1e18){ top=i-1;break;}
    }
    rep(j,2,top){
        rep(i,1,1e6){
            __int128 x=a[j].eval(i);

            if(x<=1e18) s[j].pb(x);
            else break;
        }
    }
    dwn(_,yh(),1){
        int n=yh();
        int ans=1;
        rep(i,2,top){
            ans+=upper_bound(s[i].begin(),s[i].end(),n)-s[i].begin()-1;
            // cout<<i<<" "<<upper_bound(s[i].begin(),s[i].end(),n)-s[i].begin()-1<<hvie;
        }
        cout<<ans<<hvie;
    }
    return 0;
}

题解写法

#include<bits/stdc++.h>
using namespace std;
#define int long long 
int read(){ 
	int x=0,f=1;
	char c=getchar();
	while(c<'0'||c>'9'){ 
		if(c=='-') f=-1;
		c=getchar();
	}  
	while(c>='0'&&c<='9'){
		x=x*10+c-'0';
		c=getchar();
	}  
	return x*f;
} 
//map<pair<int,int>,int> allse;   //对于去重可以利用map,也可以利用vector自带的方式去重。  如果去重后想要排序,就利用vector进行记录,利用map来 ,还有set 
const int beishu=1e12;
vector<pair<int,int> > q;
int cnt;
bool cmp(pair<int ,int> A,pair<int,int> B){
	if(A.first<B.first||A.first==B.first&&A.second<B.second) return 1;
	return 0;
} 
map<pair<int,int>,int> mp;                           
void get_allse(){
	for(int i=1;i*i<=beishu;i++){
		if(i==1){
		__int128 x=i,y=i*i*i;
		pair<int,int> a=make_pair(y,x);
		q.push_back(a);
		cnt++;
		} 
		else{
			for(__int128 x=i,y=i*i*i;y<=1e18;){
				pair<int,int> a=make_pair(y,x);
				q.push_back(a);
				cnt++;
				int temp=x;
				x=y;
				y=i*i*y-temp;
			}
		} 
		
	}
	sort(q.begin(),q.end(),cmp);   //如果用vector的话 我们无法找到对应的结果。   其实也可以、 
	q.erase(unique(q.begin(), q.end()), q.end());
	
	/*for(int i=0;i<=cnt-1;i++){
		mp[q[i]]=i;
	} */
	
} 
int find_answer(int a){
	int l=0,r=q.size()-1;
	while(l<r){
		int mid=(l+r+1)/2;
		if(q[mid].first>a) {
			r=mid-1;
		}
		else{
			l=mid;
		}
	}
	return l;
}
signed main(){
	ios::sync_with_stdio(false);
	cin.tie(0),cin.tie(0);
	get_allse();
	int T;
	cin>>T;
	for(int i=1;i<=T;i++){
		int a;
		cin>>a;
		cout<<find_answer(a)+1<<endl;
	}
	//for(int i=0;i<=cnt-1;i++){
	//	cout<<q[i].first<<"  "<<q[i].second<<endl;}
	
//	int T;
//	T=read();
	/*
	for(int i=1;i<=n;i++){
		int a;
		a=read();
		
	}	*/
}
F. 24dian

【题意】

输入 n n n m m m,给出所有使用 n n n 1 ∼ 13 1\sim 13 113数字加减乘除得到 m m m的方案

n ≤ 4 , m ≤ 1 0 9 n\leq 4,m\leq 10^9 n4,m109

【思路】

按照题意模拟即可,可以暴力枚举符号和数字。

【参考代码】

/*
 * @date:2021-07-24 12:22:37
 * @source:
*/
#include <bits/stdc++.h>

using namespace std;

typedef pair<int, int> pii;
typedef long long ll;
typedef pair<ll, ll> pll;
typedef vector<int> vi;
#define fir first
#define sec second
#define ALL(x) (x).begin(), (x).end()
#define SZ(x) (int)x.size()
#define For(i, x) for (int i = 0; i < (x); ++i)
#define Trav(i, x) for (auto & i : x)
#define pb push_back
template<class T, class G> bool chkMax(T &x, G y) {
    return y > x ? x = y, 1 : 0;
}
template<class T, class G> bool chkMin(T &x, G y) {
    return y < x ? x = y, 1 : 0;
}

int N, M;
struct Pair {
    int a, b, c, d;
};
vector<Pair> v;

int opt[5], B[5];
double A[5];
char op[] = "+-*/";
bool f1, f2;

double calculate(double x, double y, int op) {
    double s;
    switch (op) {
    case 0:
        s = x + y;
        break;
    case 1:
        s = x - y;
        break;
    case 2:
        s = x * y;
        break;
    case 3:
        s = x / y;
        break;
    }
    return s;
}
//对应公式1 ((A#B)#C)#D
void cal1() {
    double t1 = calculate(A[0], A[1], opt[0]);
    if (opt[0] == 3 && fabs(A[1]) < 1e-8) return ;
    double t2 = calculate(t1, A[2], opt[1]);
    if (opt[1] == 3 && fabs(A[2]) < 1e-8) return ;
    double t3 = calculate(t2, A[3], opt[2]);
    if (opt[2] == 3 && fabs(A[3]) < 1e-8) return ;
    if(fabs(t3 - M) > 1e-8) return ;
    f1 = 1;
    if (opt[0] != 3 && opt[1] != 3) f2 = 0;
    if (opt[0] == 3) f2 &= fabs(A[0] / A[1] - floor(A[0] / A[1])) > 1e-8;
    if (opt[1] == 3) f2 &= fabs(t1 / A[2] - floor(t1 / A[2])) > 1e-8;
}
//对应公式2 (A#(B#C))#D
void cal2() {
    double t1 = calculate(A[1], A[2], opt[1]);
    if (opt[1] == 3 && fabs(A[2]) < 1e-8) return ;
    double t2 = calculate(A[0], t1, opt[0]);
    if (opt[0] == 3 && fabs(t1) < 1e-8) return ;
    double t3 = calculate(t2, A[3], opt[2]);
    if (opt[2] == 3 && fabs(A[3]) < 1e-8) return ;
    if(fabs(t3 - M) > 1e-8) return ;
    f1 = 1;
    if (opt[1] != 3 && opt[0] != 3) f2 = 0;
    if (opt[1] == 3) f2 &= fabs(A[1] / A[2] - floor(A[1] / A[2])) > 1e-8;
    if (opt[0] == 3) f2 &= fabs(A[0] / t1 - floor(A[0] / t1)) > 1e-8;
}
//对应公式3 A#(B#(C#D))
void cal3() {
    double t1 = calculate(A[2], A[3], opt[2]);
    if (opt[2] == 3 && fabs(A[3]) < 1e-8) return ;
    double t2 = calculate(A[1], t1, opt[1]);
    if (opt[1] == 3 && fabs(t1) < 1e-8) return ;
    double t3 = calculate(A[0], t2, opt[0]);
    if (opt[0] == 3 && fabs(t2) < 1e-8) return ;
    if(fabs(t3 - M) > 1e-8) return ;
    f1 = 1;
    if (opt[2] != 3 && opt[1] != 3) f2 = 0;
    if (opt[2] == 3) f2 &= fabs(A[2] / A[3] - floor(A[2] / A[3])) > 1e-8;
    if (opt[1] == 3) f2 &= fabs(A[1] / t1 - floor(A[1] / t1)) > 1e-8;
}
//对应公式4 A#((B#C)#D)
void cal4() {
    double t1 = calculate(A[1], A[2], opt[1]);
    if (opt[1] == 3 && fabs(A[2]) < 1e-8) return ;
    double t2 = calculate(t1, A[3], opt[2]);
    if (opt[2] == 3 && fabs(A[3]) < 1e-8) return ;
    double t3 = calculate(A[0], t2, opt[0]);
    if (opt[0] == 3 && fabs(t2) < 1e-8) return ;
    if(fabs(t3 - M) > 1e-8) return ;
    f1 = 1;
    if (opt[1] != 3 && opt[2] != 3) f2 = 0;
    if (opt[1] == 3) f2 &= fabs(A[1] / A[2] - floor(A[1] / A[2])) > 1e-8;
    if (opt[2] == 3) f2 &= fabs(t1 / A[3] - floor(t1 / A[3])) > 1e-8;
}
//对应公式5 (A#B)#(C#D)
void cal5() {
    double t1 = calculate(A[0], A[1], opt[0]);
    if (opt[0] == 3 && fabs(A[1]) < 1e-8) return ;
    double t2 = calculate(A[2], A[3], opt[2]);
    if (opt[2] == 3 && fabs(A[3]) < 1e-8) return ;
    double t3 = calculate(t1, t2, opt[1]);
    if (opt[1] == 3 && fabs(t2) < 1e-8) return ;
    if(fabs(t3 - M) > 1e-8) return ;
    f1 = 1;
    if (opt[0] != 3 && opt[2] != 3) f2 = 0;
    if (opt[0] == 3) f2 &= fabs(A[0] / A[1] - floor(A[0] / A[1])) > 1e-8;
    if (opt[2] == 3) f2 &= fabs(A[2] / A[3] - floor(A[2] / A[3])) > 1e-8;
}

void dfs(int x) {
    if (f1 && !f2) return ;
    if (x == 3) {
        cal1(); cal2(); cal3(); cal4(); cal5();
        return ;
    }
    for (int i = 0; i < 4; ++i) {
        opt[x] = i;
        dfs(x + 1);
    }
    return ;
}

bool solve() {
    for (int i = 0; i < 4; ++i) A[i] = B[i];
    f1 = 0, f2 = 1;
    do {
        dfs(0);
    } while (next_permutation(A, A + 4));
    return f1 && f2;
}

void dfs1(int x) {
    if (x == N) {
        if (solve()) v.pb({B[0], B[1], B[2], B[3]});
        return ;
    }
    for (int i = !x ? 1 : B[x - 1]; i <= 13; ++i) {
        B[x] = i;
        dfs1(x + 1);
    }
}

int main() {
    scanf("%d%d", &N, &M);
    dfs1(0);
    printf("%d\n", SZ(v));
    Trav(x, v) {
        printf("%d %d %d %d\n", x.a, x.b, x.c, x.d);
    }
    return 0;
}
G. Yu Ling(Ling YueZheng) and Colorful Tree

【题目】在这里插入图片描述
在这里插入图片描述

【思路】
有点暴力:

观察到0操作影响的是所有的儿子,而1操作实际上可以转化为所有影响到这个点的最深的点,于是我们先处理出一个dfs序。

那么对0操作,直接枚举约数,修改它子树中所有约数的答案。

对1操作,我们就是查找一个点的答案。

上面的东西可以用树套树维护。

可以先预处理出一个数所有的约数,这样预处理的复杂度是 O ( n log ⁡ n ) O(n\log n) O(nlogn)(枚举倍数)。单次修改复杂度是 O ( log ⁡ 3 n ) O(\log^3 n) O(log3n),询问的复杂度是 O ( log ⁡ 2 n ) O(\log^2n) O(log2n)

复杂度 O ( n log ⁡ n + q log ⁡ 3 n ) O(n\log n+q\log^3n) O(nlogn+qlog3n),能过。

上面这个太暴力了,题解一定是更暴力的做法的优化:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
【参考代码】

树套树

#include<bits/stdc++.h>
#define rep(i,a,b) for(int i=(a),i##ss=(b);i<=i##ss;i++)
#define dwn(i,a,b) for(int i=(a),i##ss=(b);i>=i##ss;i--)
#define deb(x) cerr<<(#x)<<":"<<(x)<<'\n'
#define pb push_back
#define fi first
#define se second
#define hvie '\n'
using namespace std;
typedef pair<int,int> pii;
typedef long long ll;
typedef unsigned long long ull;
typedef double db;
int yh(){
    int ret=0;bool f=0;char c=getchar();
    while(!isdigit(c)){if(c==EOF)return -1;if(c=='-')f=1;c=getchar();}
    while(isdigit(c))ret=(ret<<3)+(ret<<1)+(c^48),c=getchar();
    return f?-ret:ret;
}
const int maxn=150005;
vector<int>P[maxn];
vector<pii>adj[maxn];
ll dep[maxn];
int root[maxn<<2],ls[maxn*262],rs[maxn*262];
ll mx[(maxn<<3)*33];
int cnt=0;
int n,q;
#define mid ((l+r)>>1)
ll ask(int x,int l,int r,int al,int ar){
    if(x==0) return 0;
    if(al<=l&&ar>=r) return mx[x];
    ll ma=0;
    if(al<=mid) ma=max(ask(ls[x],l,mid,al,ar),ma);
    if(ar>mid) ma=max(ask(rs[x],mid+1,r,al,ar),ma);
    return ma;
}

void chg(int &x,int l,int r,int p,ll val){
    // cout<<x<<" "<<l<<" "<<r<<" "<<p<<" "<<val<<"*"<<hvie;
    if(!x){
        x=++cnt;
        mx[x]=ls[x]=rs[x]=0;
    }
    mx[x]=max(mx[x],val);
    if(l==r)return;
    (p<=mid)?chg(ls[x],l,mid,p,val):chg(rs[x],mid+1,r,p,val);
}
vector<int>vis;

void add(int v,int l,int r,int al,int ar,int x,ll val){
    // cout<<v<<" "<<x<<" "<<al<<" "<<ar<<" "<<val<<hvie;
    if(al<=l&&ar>=r){
        vis.pb(v);
        chg(root[v],1,n,x,val);
        return;
    }
    if(al<=mid) add(v<<1,l,mid,al,ar,x,val);
    if(ar>mid) add(v<<1|1,mid+1,r,al,ar,x,val);
}
ll query(int v,int l,int r,int p,int al,int ar){
    if(l==r) return ask(root[v],1,n,al,ar);
    return max(ask(root[v],1,n,al,ar),p<=mid?query(v<<1,l,mid,p,al,ar):query(v<<1|1,mid+1,r,p,al,ar));
}
int L[maxn],R[maxn],dfntot=0;
void dfs(int x,int fa){
    L[x]=++dfntot;
    for(auto e:adj[x]){
        if(e.fi!=fa){
            dep[e.fi]=dep[x]+e.se;
            dfs(e.fi,x);
        }
    }
    R[x]=dfntot;
}
struct node{
    int u,l,r,x,id;
};
vector<node>Q[maxn];
ll ans[maxn],sum[maxn];int totques=0;
int main(){
    // freopen("my.in","r",stdin);
    n=yh(),q=yh();
    rep(i,1,n){
        for(int j=i;j<=n;j+=i) P[j].pb(i);
    }
    rep(i,2,n){
        int x=yh(),y=yh(),w=yh();adj[x].pb({y,w});adj[y].pb({x,w});
    }
    int op,u,l,r,x;
    dep[1]=1;
    dfs(1,0);
    rep(_,1,q){
        op=yh();u=yh();l=yh();
        if(op==0){
            for(int k:P[l]){
                Q[k].pb({u,0,0,l,0});
            }
        }
        else{
            r=yh();x=yh();
            Q[x].pb({u,l,r,x,++totques});
        }
    }
    rep(i,1,n){
        cnt=0;
        vis.clear();
        for(auto q:Q[i]){
            // cout<<i<<" : "<<q.u<<" "<<q.l<<" "<<q.r<<" "<<q.x<<" "<<q.id<<hvie;
            if(q.id==0){
                int u=q.u, x=q.x;
                // cout<<u<<" "<<x<<endl;
                add(1,1,n,L[u],R[u],x,dep[u]);
            }
            else{
                int u=q.u,l=q.l,r=q.r;
                sum[q.id]=dep[u];
                ans[q.id]=query(1,1,n,L[u],l,r);
            }
        }
        for(int u:vis) root[u]=0;
    }
    rep(i,1,totques){
        if(!ans[i]) puts("Impossible!");
        else{
            cout<<sum[i]-ans[i]<<hvie;
        }
    }
    return 0;
}

std

#include<bits/stdc++.h>
using namespace std;

const int N=110005;
int n,Q,dfn[N],ed[N];
vector<pair<int,int> > e[N];
int fa[N][20],dep[N];
long long dis[N];
void dfs(int x){
    dfn[x]=++*dfn;
    for (auto i:e[x])
        if (i.first!=fa[x][0]){
            fa[i.first][0]=x;
            dep[i.first]=dep[x]+1;
            dis[i.first]=dis[x]+i.second;
            dfs(i.first);
        }
    ed[x]=*dfn;
}

vector<int> di[N];
void init(){
    for (int i=1;i<N;i++)
        for (int j=i;j<N;j+=i)
            di[j].push_back(i);
}

struct node{
    int x,l,r,id;
};
vector<node> op[N];
long long ans[N];

const int M=20000005;
int ls[M],rs[M],S[M],nd;
int rt[N];
void change(int &k,int l,int r,int x,int v){
    if (!k) k=++nd; S[k]+=v;
    if (l==r) return;
    int mid=(l+r)/2;
    if (x<=mid) change(ls[k],l,mid,x,v);
    else change(rs[k],mid+1,r,x,v);
}
int ask(int k,int l,int r,int x,int y){
    if ((x<=l&&r<=y)||!k) return S[k];
    int mid=(l+r)/2;
    if (y<=mid) return ask(ls[k],l,mid,x,y);
    if (x>mid) return ask(rs[k],mid+1,r,x,y);
    return ask(ls[k],l,mid,x,mid)+ask(rs[k],mid+1,r,mid+1,y);
}
void erase(int x,int y){
    for (;x<=n;x+=x&(-x)) rt[x]=0;
}
void change(int x,int y,int v){
    //cout<<"C"<<x<<' '<<y<<' '<<v<<endl;
    for (;x<=n;x+=x&(-x)) change(rt[x],1,n,y,v);
}
int query(int x,int l,int r){
    //cout<<"Q"<<x<<' '<<l<<' '<<r<<endl;
    int s=0;
    for (;x;x-=x&(-x)) s+=ask(rt[x],1,n,l,r);
    return s;
}
void solve(int x){
    for (auto i:op[x])
        if (!i.id){
            change(dfn[i.x],i.l,1);
            change(ed[i.x]+1,i.l,-1);
        }
        else{
            int p=i.x;
            int val=query(dfn[p],i.l,i.r);
            //cout<<val<<endl;
            if (!val){
                ans[i.id]=-1;
                continue;
            }
            for (int j=16;j>=0;j--)
                if (fa[p][j])
                    if (query(dfn[fa[p][j]],i.l,i.r)==val)
                        p=fa[p][j];
            ans[i.id]=dis[i.x]-dis[p];
            //cout<<i.x<<' '<<p<<' '<<dis[i.x]<<' '<<dis[p]<<endl;
        }
    
    for (;nd;--nd) ls[nd]=rs[nd]=S[nd]=0;
    for (auto i:op[x])
        if (!i.id){
            erase(dfn[i.x],i.l);
            erase(ed[i.x]+1,i.l);
        }
}
int main(){
    init();
    scanf("%d%d",&n,&Q);
    for (int i=1;i<n;i++){
        int x,y,v;
        scanf("%d%d%d",&x,&y,&v);
        e[x].push_back(pair<int,int>(y,v));
        e[y].push_back(pair<int,int>(x,v));
    }
    dfs(1);
    
    for (int i=1;i<=18;i++)
        for (int j=1;j<=n;j++)
            fa[j][i]=fa[fa[j][i-1]][i-1];
    
    for (int i=1;i<=Q;i++){
        int tp,a,b,c,d;
        scanf("%d%d%d",&tp,&a,&b);
        if (tp==0){
            for (auto j:di[b])
                op[j].push_back((node){a,b,0,0});
            ans[i]=(-(1ll<<62));
        }
        else{
            scanf("%d%d",&c,&d);
            op[d].push_back((node){a,b,c,i});
        }
    }
    
    for (int i=1;i<=n;i++)
        solve(i);

    for (int i=1;i<=Q;i++)
        if (ans[i]!=-(1ll<<62))
            if (ans[i]<0) puts("Impossible!");
            else printf("%lld\n",ans[i]);
}
H. Ling Qiu, Luna and Triple Backpack

【题目】
在这里插入图片描述

在这里插入图片描述

【思路】
题解有三页字很小的PDF呢!
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
【参考代码】

#include<bits/stdc++.h>
using namespace std;

long long A,D;
int n,a[310],b[310],c[310],d[310],id[310],sum,sum3,sum33,bas,s[310];
unsigned long long f[310][1693][29];
short g[310][51][5010],h[310][5010];

bool cmp(int x,int y) {return b[x]<b[y];}

int main()
{
    scanf("%d",&n),A=D=0,sum=0;
    for (int i=1; i<=n; i++)
    {
        scanf("%d%d%d%d",&a[i],&b[i],&c[i],&d[i]);
        A=max(1ll*a[i],A),D+=d[i],id[i]=i,sum+=c[i];
    }
    sum3=sum/3+24,sum33=sum3/64,bas=sum/3-24;
    sort(id+1,id+1+n,cmp);
    f[0][0][0]=1,s[0]=0;
    long long ans=1000000000000000000ll;
    for (int i=1; i<n; i++)
    {
        int C=c[id[i]];
        s[i]=s[i-1]+C;
        //printf("%d %d\n",id[i],g[1][);
        for (int j=0; j<=sum3-C; j++)
            for (int k=0; k<=sum33; k++) f[i][j+C][k]|=f[i-1][j][k];
        for (int j=0; j<=sum3; j++)
            for (int k=0; k<=sum33; k++)
            {
                f[i][j][k]|=f[i-1][j][k];
                f[i][j][k]|=(f[i-1][j][k]&((1ull<<(64-C))-1))<<C;
                f[i][j][k+1]|=(f[i-1][j][k]>>(64-C));
            }
        for (int j=max(bas-C,0); j<=sum3-C; j++)    
            for (int k=0; k<=sum3; k++) 
                if (1&(f[i-1][j][k>>6]>>(k&63))) 
                    g[i][j+C-bas][k]=max(g[i][j+C-bas][k],(short)(n+1-i));
        for (int j=0; j<=48; j++)
        {
            for (int k=0; k<=sum3; k++)
                g[i][j][k]=max(g[i][j][k],g[i-1][j][k]);
            for (int k=C; k<=sum3; k++)
            {
                g[i][j][k]=max(g[i][j][k],g[i-1][j][k-C]);
                if (g[i-1][j][k-C]) 
                {
                    ans=min(ans,(1ll*b[id[i]]+b[id[n+1-g[i-1][j][k-C]]]+b[id[n]])*(max(bas+j,max(k,sum-k-bas-j))));
                    //if ((1ll*b[i]+b[n+1-g[i-1][j][k-C]]+b[n])*(max(bas+j,max(k,sum-k-bas-j)))==264) printf("%d %d %d %d %d\n",i,j,k,C,g[i-1][j][k-C]);
                }
            }
        }
    }
    for (int i=n-1; i>1; i--)
    {
        int C=c[id[i]];
        h[i][C]=n+1-i;
        for (int j=0; j<=sum; j++) h[i][j]=max(h[i+1][j],h[i][j]);
        for (int j=0; j<=sum-C; j++) h[i][j+C]=max(h[i+1][j],h[i][j+C]);
        for (int j=0; j<=sum; j++) if (h[i][j])
        {
            ans=min(ans,(1ll*b[id[n]]+b[id[n+1-h[i][j]]]+b[id[i-1]])*(max(j,max(sum-s[i-1]-j,s[i-1]))));
        }
    }
    //#define __u128int_t long long
    __uint128_t Ans=((__uint128_t)A)*((__uint128_t)ans)*((__uint128_t)D);
    //cout<<ans<<endl;
    int o[110],oN=0;
    while (Ans) o[++oN]=Ans%10,Ans/=10;
    for (int i=oN; i; i--) putchar(o[i]+'0');
    putchar('\n');
    return 0;
}
I. Kuriyama Mirai and Exclusive Or

【题意】

给定一个长度为 n n n的序列 a i a_i ai,有两种操作:

  • [ l , r ] [l,r] [l,r]区间每个数异或上 x x x
  • ∀ i ∈ [ l , r ] \forall i\in[l,r] i[l,r],给 a i a_i ai异或上 ( x + ( i − l ) ) (x+(i-l)) (x+(il))

最后求每个位置的值。

n ≤ 6 × 1 0 5 , q ≤ 4 × 1 0 5 , a < 2 30 n\leq 6\times 10^5,q\leq 4\times 10^5,a<2^{30} n6×105,q4×105,a<230

【思路】

先说说我的两个log做法,然而属实卡不过去。

这题很关键的一个点是最后求权值,就在告诉我们可以离线搞。

首先这种求权值的东西一般都是拆开按位考虑的。第一种操作可以通过简单的差分来做,关键是第二种操作。

我们考虑 x = 0 x=0 x=0时,这个贡献的形式。

对于第 0 0 0位,可以发现从 l l l开始的贡献是01010101;

对于第 1 1 1位,可以发现从 l l l开始的贡献是00110011;

对于第 2 2 2位,可以发现从 l l l开始的贡献是00001111;

这个贡献的规律是由于进位导致的,而实际上加上 x x x后,贡献的形式不变,只是起始位置变了,然后有一个作用的区间。

然后,对于第 i i i位二进制,我们如果把贡献的起始点全部移动到1,实际上可以看成有 2 i + 1 2^{i+1} 2i+1种标记(就是上面的贡献位移),而不难看出,实际上对于这些标记,有贡献的是对应回区间以后1的位置,这些位置组成了一个或两个连续的区间。于是我们可以用树状数组来维护标记个数和,这样最后也是区间求值就行了。

需要注意的是高位二进制的标记类型很多,但实际上只有至多 2 q 2q 2q种标记,那么离散化以后就一样了。

复杂度 O ( q log ⁡ q log ⁡ a ) O(q\log q\log a) O(qlogqloga)

这个做法并不能通过。

实际上,我们通过前面的做法可以知道,我们只关心标记中有贡献的1的位置,又可以发现所有标记异或起来的结果,最多只有4个突变的位置,那么我们只需要维护这些突变的位置就可以了。

复杂度 O ( n log ⁡ a ) O(n\log a) O(nloga)

std给出的做法也是基于这个贡献考虑的,不过它通过二维差分和二维后缀和统计答案。

这题因为std写法特殊,还有点卡空间- -

【参考代码】

2log

#include<bits/stdc++.h>
#define pb push_back
#define mkp make_pair
#define fi first
#define se second
#define rg register
using namespace std;

typedef double db;
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<int,ll> pil;
const int N=6e5+10,M=31;
vector<int>vec,q[N];
int n,Q;
int ss[N],fc[M],ans[N],a[N];

int read()
{
    int ret=0,f=1;char c=getchar();
    while(!isdigit(c)) {if(c=='-')f=0;c=getchar();}
    while(isdigit(c)) ret=ret*10+(c^48),c=getchar();
    return f?ret:-ret;
}

struct BIT
{
    #define lowbit(x) ((x)&(-x))
    int c[N],MX;
    void clear(int x){for(int i=0;i<=MX;++i)c[i]=0;MX=x;}
    void insert(int x)
    {
        x=lower_bound(vec.begin(),vec.end(),x)-vec.begin()+1;
        //printf("insert:%d\n",x);
        for(;x<MX;x+=lowbit(x)) c[x]^=1;
    }
    int query(int x)
    {
        int ret=0;
        for(;x>0;x-=lowbit(x)) ret^=c[x];
        return ret;
    }
    int nowquery(int l,int r)
    {

        int lp=lower_bound(vec.begin(),vec.end(),l)-vec.begin()+1;
        int rp=upper_bound(vec.begin(),vec.end(),r)-vec.begin();
        //printf("nowquery:%d [%d-1,%d] query[l-1]:%d query[r]:%d\n",p,l,r,query(lp-1),query(rp));
        return query(rp)^query(lp-1);
    }
    int Query(int l,int r,int add)
    {
        if(l<0) return nowquery(0,r)^nowquery(l+add,add-1);
        else return nowquery(l,r);
    }
}bit;

void write(int x)
{
    if(x>9) write(x/10);
    putchar(x%10^48);
}
void writesp(int x)
{
    write(x);putchar(' ');
}

int main()
{
    freopen("i.in","r",stdin);
    freopen("i.out","w",stdout);
    for(int i=0;i<M;++i) fc[i]=1<<i;
    n=read();Q=read();
    for(int i=1;i<=n;++i) a[i]=read();
    while(Q--)
    {
        int op=read(),l=read(),r=read(),x=read();
        if(!op)
        {
            ss[l]^=x;
            ss[r+1]^=x;
        }
        else
        {
            q[l].pb(x);
            q[r+1].pb(x+(r-l+1));
        }
    }
    for(rg int i=1;i<=n;++i) ss[i]^=ss[i-1];
    for(rg int p=0;p<M-1;++p)
    {
        //cerr<<p<<" "<<clock()<<endl;
        vec.clear();
        for(rg int i=1;i<=n;++i)
        {
            for(auto x:q[i])
            {
                int typ=((-((i-x)-1))%fc[p+1]+fc[p+1])%fc[p+1];
                vec.pb(typ);
            }
        }
        vec.pb(fc[p+1]);
        sort(vec.begin(),vec.end());
        vec.erase(unique(vec.begin(),vec.end()),vec.end());
        //cerr<<p<<" "<<clock()<<endl;

        bit.clear(vec.size()+1);
        //printf("size:%d\n",bit.MX);
        for(rg int i=1;i<=n;++i)
        {
            for(auto x:q[i])
            {
                int typ=((-((i-x)-1))%fc[p+1]+fc[p+1])%fc[p+1];
                bit.insert(typ);
               // printf("insert_p: %d, %d,%d\n",p,i,typ);
            }
            int t=(i-1)%fc[p+1];
            //printf("for [%d,%d] query:%d %d\n",i,p,fc[p]-t,fc[p+1]-t-1);
            int ans1=bit.Query(fc[p]-t,fc[p+1]-t-1,fc[p+1]);
            ans[i]^=ans1*fc[p];
        }
    }  
    /*for(int i=1;i<=n;++i) 
    {
        //ans[i]^=ans[i-1];
        printf("%d ",ans[i]);
    }
    puts("");*/
    for(int i=1;i<=n;++i) writesp(ans[i]^ss[i]^a[i]);
    cerr<<clock()<<endl;
    return 0;
}

1log维护突变

#include <stdio.h>
#include <vector>
#define MN 600000
#define MQ 400000
const int B = 21;

using std::vector;

struct Opt{
    int o,l,r,x;
}q[MQ+5];

int n,Q,a[MN+5];
vector<int> t[MN+5];

bool ch[1<<(B+1)],*f[B];
int mask[B];

void proc1(int& i,int& lx,int o,int l,int r,int x){
    if(o==0){
        if(i==l||i==r+1) lx ^= x;
    }else{
        int cp = 1;
        for(int j=0;j<30;j++){
            if(i==l) lx ^= x&(1<<j);
            if(i==r+1) lx ^= (x+r-l+1)&(1<<j);
            if(j>=B&&l+cp<=r+1){
                if(i==l+cp) lx ^= 1<<j;
            }
            if((x&(1<<j))==0) cp += (1<<j);
        }
    }
}

void proc2(int& i,int& lx,int o,int l,int r,int x){
    if(o==1){
        int cp = 1;
        for(int j=0;j<B;j++){
            if(i==l||i==r+1) f[j][(l+cp)&((1<<j)-1)] ^= 1;
            if((x&(1<<j))==0) cp += (1<<j);
        }
    }
}

int main(){
    for(int i=0;i<B;i++){
        f[i] = &ch[1<<i];
        mask[i] = (1<<i)-1;
    }
    scanf("%d%d",&n,&Q);
    for(int i=1;i<=n;i++)
        scanf("%d",&a[i]);
    for(int i=1;i<=Q;i++){
        scanf("%d%d%d%d",&q[i].o,&q[i].l,&q[i].r,&q[i].x);
        t[q[i].l].push_back(i);
        t[q[i].r+1].push_back(i);
        if(q[i].o==1){
            int cp = 1;
            for(int j=0;j<B;j++){
                if((q[i].x&(1<<j))==0) cp += (1<<j);
            }
            for(int j=B;j<30;j++){
                if(q[i].l+cp<q[i].r+1){
                    t[q[i].l+cp].push_back(i);
                    break;
                }
                if((q[i].x&(1<<j))==0) break;
            }
        }
    }
    for(int i=1,x=0;i<=n;i++){
        for(int id:t[i]){
            proc1(i,x,q[id].o,q[id].l,q[id].r,q[id].x);
        }
        for(int j=0;j<B;j++)
            if(f[j][i&mask[j]])
                x ^= 1<<j;
        a[i] ^= x;
        for(int id:t[i]){
            proc2(i,x,q[id].o,q[id].l,q[id].r,q[id].x);
        }
    }
    for(int i=1;i<=n;i++)
        printf("%d%c",a[i]," \n"[i==n]);
}

std

#include <bits/stdc++.h>
using namespace std;

const int L = 1 << 9, mask = L - 1;
int main(){
    freopen("i.in","r",stdin);
    freopen("is.out","w",stdout);
    cin.tie(nullptr);
    ios::sync_with_stdio(false);
    int N, Q;
    cin >> N >> Q;
    vector<int> A(N);
    for(int& a : A) cin >> a;
    vector<bitset<L>> lo(N + 1);
    vector<int> hi(N + 1);
    while(Q--){
        int t, l, r, x;
        cin >> t >> l >> r >> x;
        l--;
        if(t == 0){
            hi[l] ^= x;
            hi[r] ^= x;
        }
        else{
            lo[l][x - l & mask] = !lo[l][x - l & mask];
            lo[r][x - l & mask] = !lo[r][x - l & mask];
            int X = x & ~mask;
            hi[l] ^= X;
            for(int i = l - (x & mask) + L; i < r; i += L){
                hi[i] ^= X;
                hi[i] ^= X += L;
            }
            hi[r] ^= X;
        }
    }
    for(int i = 0; i < N; i++){
        lo[i + 1] ^= lo[i];
        hi[i + 1] ^= hi[i];
        A[i] ^= hi[i];
        for(int j = 0; j < L; j++) if(lo[i][j]) A[i] ^= i + j & mask;
        cout << A[i] << " \n"[i + 1 == N];
    }
    cerr<<clock()<<endl;
}

J. Counting Triangles

【题意】

一幅 n n n个点的完全图,每条边要么是白色要么是黑色,问有多少个同色三角形。

n ≤ 8000 n\leq 8000 n8000

【思路】

一个性质是,每个三角形要么三边同色,要么两边同色另一边异色,对于后者,三角形恰有两个异色角,而前者没有异色角。

因此异色角数除以2即为不符合条件的三角形个数,总数减去即可。

【参考代码】

#include <bits/stdc++.h>
using namespace std;

typedef long long ll;
const int mac = 8e3 + 10;

bool color[mac][mac];//0为白色,1为黑色

namespace GenHelper {
unsigned z1, z2, z3, z4, b, u;
unsigned get() {
    b = ((z1 << 6)^z1) >> 13;
    z1 = ((z1 & 4294967294U) << 18)^b;
    b = ((z2 << 2)^z2) >> 27;
    z2 = ((z2 & 4294967288U) << 2)^b;
    b = ((z3 << 13)^z3) >> 21;
    z3 = ((z3 & 4294967280U) << 7)^b;
    b = ((z4 << 3)^z4) >> 12;
    z4 = ((z4 & 4294967168U) << 13)^b;
    return (z1 ^ z2 ^ z3 ^ z4);
}
bool read() {
    while (!u) u = get();
    bool res = u & 1;
    u >>= 1; return res;
}
void srand(int x) {
    z1 = x;
    z2 = (~x) ^ 0x233333333U;
    z3 = x ^ 0x1234598766U;
    z4 = (~x) + 51;
    u = 0;
}
}
using namespace GenHelper;

ll pw(int x) {
    return 1LL * x * x;
}

int main() {
    int n, seed;
    cin >> n >> seed;
    srand(seed);
    for (int i = 0; i < n; i++)
        for (int j = i + 1; j < n; j++)
            color[j][i] = color[i][j] = read();
    ll ans = 1LL * n * (n - 1) * (n - 2) / 6;
    ll res = 0;
    for (int i = 0; i < n; i++) {
        int black = 0, white = 0;
        for (int j = 0; j < n; j++) {
            if (i == j) continue;
            if (color[i][j]) {
                res += white;
                black++;
            } else {
                res += black;
                white++;
            }
        }
    }
    ans -= res / 2;
    printf("%lld\n", ans);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值