复习

整除分块

\(O(\sqrt n)\) 复杂度快速计算 \(\sum\limits_{i=1}^n \lfloor n/i\rfloor\)

分块:\(\forall i\in [l,r],\lfloor n/i\rfloor=C\)
枚举 \(l=1\cdots n\)

推导 \(r\) 的过程:
\(\lfloor n/l\rfloor=\lfloor n/r\rfloor=C\)
\(lC\le n,rC\le n,(r+1)C>n\)
\(r\in (\lfloor n/C\rfloor-1,\lfloor n/C\rfloor]\)
\(\therefore r=\lfloor n/C\rfloor=\lfloor n/\lfloor n/l\rfloor\rfloor\)
结论:\(\forall i\in [l,\lfloor n/\lfloor n/l\rfloor\rfloor],\lfloor n/i\rfloor=\mathrm{constant}\)

模板:[CQOI2007]余数求和

#include<bits/stdc++.h>

using namespace std;

#define int long long

int s(int l,int r){
	return (l+r)*(r-l+1)/2;
}

int j(int n,int k){
	int ans=n*k;
	for(int l=1,r=1;l<=n;l=r+1){
		r=k/l?min(k/(k/l),n):n;
		ans-=s(l,r)*(k/l);
	}
	return ans;
}

signed main(){
	int n,k; scanf("%lld%lld",&n,&k);
	printf("%lld",j(n,k));
	return 0;
}

注意事项:计算除法时要注意除数不能为零


差分约束

\(O(nm)\) 复杂度解 \(x_i-x_j\le \Delta\) 的不等式组问题

考虑 Bellman_Ford 算法中的松弛操作中的三角形不等式:\(dis_u\le dis_v+w\)
将原不等式组全部变形为 \(x_i\le x_j+\Delta\),可以直观地发现两者非常类似。
由此,我们将 \(x_i\) 看作图中的结点,对于每一个约束条件 \(x_i\le x_j+\Delta\),连一条 \((j,i,\Delta)\) 的有向边。

为什么是有向边
显然,\(x_j+\Delta\) 只能说明 \(j\)\(i\) 有一条权值为 \(\Delta\) 的边,反之则不能。
特别地,有时在添加所有约束条件后整个图仍未联通。不失一般性,为防止这种情况出现,需要添加一个虚点,并将这个虚点与其他的所有点连一条权值为零的有向边。
通过这一过程,我们便将原来的代数问题转化为图论的最短路问题,即可在 \(O(nm)\) 的时间复杂度内解决此问题。
需要注意,可能会出现无解的情况,在图论的意义上即是产生负环。

模板:
LuoguP3385 【模板】负环

#include<bits/stdc++.h>

using namespace std;

const int maxn=2005;
const int maxm=3005;
const int INF=0x3f3f3f3f;
struct Edge{
	int frm;
	int to;
	int nxt;
	int val;
}edge[maxm<<1];
int tot,hd[maxn],dis[maxn];
int n,m;

void adde(int u,int v,int w){
	edge[++tot].frm=u;
	edge[tot].to=v;
	edge[tot].nxt=hd[u];
	edge[tot].val=w;
	hd[u]=tot;
}

bool bellman_ford(){
	for(int i=1;i<=n;i++)
		for(int j=1;j<=tot;j++){
			if(dis[edge[j].frm]!=INF&&dis[edge[j].frm]+edge[j].val<dis[edge[j].to]){
				if(i==n)return true;
				dis[edge[j].to]=dis[edge[j].frm]+edge[j].val;					
			}
		}
	return false;
}

int main(){
	int T; scanf("%d",&T);
	while(T--){
		tot=0;
		memset(hd,0,sizeof hd);
		memset(dis,INF,sizeof dis);
		dis[1]=0;
		scanf("%d%d",&n,&m);
		for(int i=1;i<=m;i++){
			int u,v,w; scanf("%d%d%d",&u,&v,&w);
			adde(u,v,w);
			if(w>=0)adde(v,u,w);
		}
		bool ok=bellman_ford();
		printf("%s\n",ok?"YES":"NO");
	}
	return 0;
}

LuoguP1993 小 K 的农场

#include<cstdio>
#include<queue>
#include<cstring>

using namespace std;

#define rep(i,a,b) for(int i=(a);i<=(b);i++)
template<typename S,typename T>bool up(S&a,T b){return (a<b)?(a=b,1):0;}
template<typename S,typename T>bool down(S&a,T b){return (a>b)?(a=b,1):0;}

const int maxn=5e3+5;
int S,n,m,dis[maxn],cnt[maxn];
bool inq[maxn];
struct Edge{
	int to;
	int nxt;
	int val;
}edge[maxn<<1];
int tot,hd[maxn];

void adde(int u,int v,int w){
	edge[++tot].to=v;
	edge[tot].nxt=hd[u];
	edge[tot].val=w;
	hd[u]=tot;
}

bool SPFA(){
	queue<int> q;
	q.push(S);
	memset(dis,0x3f,sizeof dis);
	dis[S]=0;
	inq[S]=true; cnt[S]=0;
	while(q.size()){
		int u=q.front(); q.pop();
		inq[u]=false;
		for(int i=hd[u];i;i=edge[i].nxt){
			int v=edge[i].to;
			if(dis[v]>dis[u]+edge[i].val){
				dis[v]=dis[u]+edge[i].val;
				cnt[v]=cnt[u]+1;
				if(cnt[v]>=n) return false;
				if(!inq[v]) inq[v]=true,q.push(v);
			}
		}
	}
	return true;
}

int main(){
	scanf("%d%d",&n,&m);
	while(m--){
		int op,a,b,c;
		scanf("%d%d%d",&op,&a,&b);
		if(op==1) scanf("%d",&c),adde(a,b,-c);
		else if(op==2) scanf("%d",&c),adde(b,a,c);
		else adde(b,a,0);
	}
	S=n+1;
	rep(i,1,n) adde(S,i,0);
	printf("%s",SPFA()?"Yes":"No");
  return 0;
}

01Trie

模板:AcWing143 最大异或对
将每个数的二进制表示从高位到低位依次插入 01 Trie,使得高位的数深度小。
在线查找之前的与当前数异或值最大的数。具体地从 Trie 深度小的地方开始找当前位异或值为 1 的数,逐渐向深度大的地方查找。

#include<cstdio>
#include<algorithm>

using namespace std;

#define rep(i,a,b) for(int i=(a);i<=(b);i++)

const int maxn=1<<17;
struct Trie{
  int tr[maxn*30][2],tot;
  
  void ins(int x){
    int p=0;
    for(int i=30;i>=0;i--){
      int t=x>>i&1;
      if(!tr[p][t]) tr[p][t]=++tot;
      p=tr[p][t];
    }
  }
  
  int qry(int x){
    int p=0,res=0;
    for(int i=30;i>=0;i--){
      int t=x>>i&1; res<<=1;
      if(tr[p][t^1]) res|=1,p=tr[p][t^1];
      else p=tr[p][t];
    }
    return res;
  }
}trie;

int main(){
  int n;
  scanf("%d",&n);
  int ans=0;
  rep(i,1,n){
    int x; scanf("%d",&x);
    trie.ins(x); ans=max(ans,trie.qry(x));
  }
  printf("%d",ans);
  return 0;
}

扩展到树上同理。

AcWing144 最大异或值路径

#include<cstdio>
#include<cctype>
#include<cstring>
#include<algorithm>

using namespace std;

void rd(int&x){
    x=0; char ch=getchar();
    while(!isdigit(ch)) ch=getchar();
    while(isdigit(ch)) x=x*10+ch-'0',ch=getchar();
}

#define rep(i,a,b) for(int i=(a);i<=(b);i++)
const int maxn=1<<17;

struct Trie{
    int tr[maxn*30][2],tot;

    void ins(int x){
        int p=0;
        for(int i=30;i>=0;i--){
            int t=x>>i&1;
            if(!tr[p][t]) tr[p][t]=++tot;
            p=tr[p][t];
        }
    }

    int qry(int x){
        int p=0,res=0;
        for(int i=30;i>=0;i--){
            int t=x>>i&1; res<<=1;
            if(tr[p][t^1]) res|=1,p=tr[p][t^1];
            else p=tr[p][t];
        }
        return res;
    }
}trie;

int n,d[maxn],tot,hd[maxn];
struct Edge{
    int to;
    int val;
    int nxt;
}edge[maxn<<1];

void adde(int u,int v,int w){
    edge[++tot]=(Edge){v,w,hd[u]};
    hd[u]=tot;
}

void dfs(int u,int f,int val){
    d[u]=val;
    for(int i=hd[u];i;i=edge[i].nxt){
        int o=edge[i].to;
        if(o==f) continue;
        dfs(o,u,val^edge[i].val);
    }
}

int main(){
    rd(n);
    rep(i,1,n-1){
        int u,v,w; rd(u),rd(v),rd(w);
        u++,v++;
        adde(u,v,w);
        adde(v,u,w);
    }
    dfs(1,0,0);
    int ans=0;
    rep(i,1,n){
        trie.ins(d[i]);
        ans=max(ans,trie.qry(d[i]));
    }
    printf("%d",ans);
  return 0;
}

树状数组

\(c_i\) 维护 \([i-lowbit(i)+1,i]\) 的数的和。\(lowbit(x)=x\and (-x)\),意义是二进制表示中最低位的 \(1\)
这样表示的原理是二进制拆分理论。

树状数组能解决的基本问题是单点修改区间查询。使用差分,可以解决区间修改单点查询。
如果想要解决区间修改区间查询就要维护两个树状数组了。

解决区间修改,我们肯定要使用差分。由于差分数组的前缀和数组就是原数组,所以我们可以轻松解决单点查询。
将区间查询形式化,即是求 \(\sum\limits_{i=1}^x \sum\limits_{j=1}^i c_j\)
考虑贡献法,原式等于 \(\sum\limits_{i=1}^x (x-i+1)c_i=(x+1)\cdot \sum\limits_{i=1}^x c_i-\sum\limits_{i=1}^x i\cdot c_i\)
使用两个树状数组分别维护 \(c_i\)\(i\cdot c_i\) 即可。

代码:(抄自 OI Wiki)

// C++ Version
int t1[MAXN], t2[MAXN], n;

inline int lowbit(int x) { return x & (-x); }

void add(int k, int v) {
  int v1 = k * v;
  while (k <= n) {
    t1[k] += v, t2[k] += v1;
    k += lowbit(k);
  }
}

int getsum(int *t, int k) {
  int ret = 0;
  while (k) {
    ret += t[k];
    k -= lowbit(k);
  }
  return ret;
}

void add1(int l, int r, int v) {
  add(l, v), add(r + 1, -v);  // 将区间加差分为两个前缀加
}

long long getsum1(int l, int r) {
  return (r + 1ll) * getsum(t1, r) - 1ll * l * getsum(t1, l - 1) -
         (getsum(t2, r) - getsum(t2, l - 1));
}
posted @ 2021-10-20 23:59  Aehnuwx  阅读(31)  评论(0编辑  收藏  举报