Loading

ziwenの板子

ziwenの板子

组合数

ll f[300050];
ll rf[300050];
ll MOD = 998244353;

ll inv(int a) {
		return qpow(a, MOD-2,MOD);
	}
	
ll mul(int a,int b)
{
	return (int)((ll)a * (ll)b % MOD);
}

ll C(int n, int k) {
		return (k < 0 || k > n) ? 0 : mul(f[n], mul(rf[n-k], rf[k]));
	}
void INIT()
{
	f[0]=rf[0] = 1;
	for (int i = 1; i <= 3e5; ++i) {
			f[i] = mul(f[i-1], i);
			rf[i] = mul(rf[i-1], inv(i));
		}
}
int c[11][11];
void init()
{
    memset(c,0,sizeof(c));
    c[0][0]=c[1][0]=c[1][1]=1;
    for(int i=2;i<=10;i++)
    {
        c[i][0]=c[i][i]=1;
        for(int j=1;j<i;j++)
            c[i][j]=c[i-1][j]+c[i-1][j-1];
    }
}

注意事项

写dp要注意有没有什么东西可以状态分解,比如给出一个n*n的矩阵,要找其中同色的菱形数量。那么我们可以把菱形拆成四个方向的形状,然后再用n^2跑,从而把三方转化为平方

区间(判相交,长,合并)

对于区间  [l1,r1], [l2,r2]

两个区间相交:  	if( l1<r2 && l2<r1)

相交长度:      	inter=min(r1,r2) - max(l1,l2)

合并区间长:   	len = r1-l1+r2-l2-inter;

堆优化 - dijkstra

void dij(int u){
	priority_queue<pll,vector<pll>,greater<pll> > Q;
	rep(i,1,n) dis[i] = inf; 
	dis[u] = 0;
	Q.push(mp(dis[u],u));
	while(!Q.empty()){
		ll tmp = Q.top().first;
		u = Q.top().second;
		Q.pop();
		if(dis[u]<tmp) continue;
		for(auto i:G[u]){
			int v = i.to;
			if(dis[v] > dis[u] + i.w){
				dis[v] = dis[u] + i.w;
				Q.push(mp(dis[v],v));
			}
		}
	}
}

逆元

费马
long long inv(long long a) {
    return quickpow(a, MOD - 2);
}

ll exgcd(ll a,ll b,ll &x,ll &y){
	if(b==0){
		x=1,y=0;
		return a;
	}
	ll ret=exgcd(b,a%b,y,x);
	y-=a/b*x;
	return ret;
}

ll getInv(ll a,ll mod){
	ll x,y;
	ll d=exgcd(a,mod,x,y);
	return d==1?(x%mod + mod)%mod:-1;
}

lower_bound( )和upper_bound( )

lower_bound( begin,end,num )从数组的begin位置到end-1位置二分查找第一个大于或等于num的数字,找到返回该数字的地址,不存在则返回end。通过返回的地址减去起始地址begin,得到找到数字在数组中的下标。

upper_bound( begin,end,num )从数组的begin位置到end-1位置二分查找第一个大于num的数字,找到返回该数字的地址,不存在则返回end。通过返回的地址减去起始地址begin,得到找到数字在数组中的下标。

在从大到小的排序数组中,重载lower_bound()和upper_bound()

lower_bound( begin,end,num,greater<type>() ):从数组的begin位置到end-1位置二分查找第一个小于或等于num的数字,找到返回该数字的地址,不存在则返回end。通过返回的地址减去起始地址begin,得到找到数字在数组中的下标。

upper_bound( begin,end,num,greater<type>() ):从数组的begin位置到end-1位置二分查找第一个小于num的数字,找到返回该数字的地址,不存在则返回end。通过返回的地址减去起始地址begin,得到找到数字在数组中的下标。

	vector<int> a;
	rep(i,1,10) a.pb(i);
	int index1 = lower_bound(a.begin(),a.end(),5) - a.begin(); 
	//返回第一个大于等于5的值,所以[1 2 3 4  *5*  6 7 8 9 10]大于等于5对应的下标是4
	int index2 = upper_bound(a.begin(),a.end(),5) - a.begin(); 
	//返回第一个大于5的值,所以[1 2 3 4 5  *6*  7 8 9 10]大于5对应的下标是5

比较器

从大到小排序的set
set<int, greater<int> > s1;
set<pair<int, int>, greater<pair<int, int>>> ss;

//升序队列,小顶堆
priority_queue <int,vector<int>,greater<int> > q;  

//降序队列,大顶堆,默认从大到小排序
priority_queue <int,vector<int>,less<int> >q;

//greater和less是std实现的两个仿函数(就是使一个类的使用看上去像一个函数。其实现就是类中实现一个operator(),这个类就有了类似函数的行为,就是一个仿函数类了)

struct ss{
	int x;
	ss(int xx){ x = xx;}
	bool operator<(const ss& t) const{   //小于运算符, 
		return t.x < x;                  //传进来的数在左边。  这是小顶堆
	}
};
priority_queue<ss> Q;

离散化

n=read();
for(int i=1;i<=n;i++)
	v[i]=mp(read(),i);
sort(v+1,v+1+n);
int index=0;
for(int i=1;i<=n;i++)
{
	if(v[i].X==v[i-1].X) a[v[i].Y]=index;
	else a[v[i].Y]=++index;
}

离散化 - unique

// a[0~len-1] 是 从小到大排序后的数组, x是待查下标 
int locate(int a[],int len,int x)
{
	return lower_bound(a,a+len,x)-a;
}

int main()
{
    int a[] = {1,5,8,8,4,1,2,5,4,1};
    int b[] = {1,5,8,8,4,1,2,5,4,1};
	sort(a,a+10);
    int cnt = unique(a,a+10) - a;
    for ( int i=0; i<cnt; i++ ) {
        cout << a[i] << " ";
    }
    cout<<endl;
    
    for ( int i=0; i<10; i++ ) {
    	cout<<locate(a,cnt,b[i])<<" \n"[i==10-1]; 
	} 
    //输出:1 2 4 5 8
    //输出:0 3 4 4 2 0 1 3 2 0
    return 0;
}

循环节:

一个有n个数的环,每次循环走k步,走到每个点都有具体的权值,问在任意点出发最多走m步的情况下,一开始需要拥有多少价值才能使最终总价值不少于s。

做法: 暴力枚举所有循环节,然后单调队列找最大子段和。

#include <bits/stdc++.h>
using namespace std;
#define pb push_back
#define mp make_pair
#define ll long long
#define rep(i,x,y) for(int i=x;i<=y;i++)
const int N = 1e4+10;

int n,m,k,cnt;
ll s,MAX,v[N];
bool vis[N]; vector<ll> g[N];
ll que[N<<1],mx[N<<1],sta[N<<1];

ll solve(const vector<ll>&vv,int count){ //单调队列找到长度最大为count的 最大子段和 
	int sz=vv.size();
	for(int i=0;i<sz;i++)
		que[i] = que[i+sz] = vv[i]; //复制一倍 
	sz = sz<<1;
	int st = 0,ed=0;
	ll res = 0;
	for(int i=0;i<sz;i++){	//开始单调队列 
		if(i==0)
			mx[i] = que[i];
		else 
			mx[i] = mx[i-1]+que[i];
		if(i<count)
			res=max(res,mx[i]);
		while(st<ed && sta[st]+count<i)
			st++;
		if(st<ed)
			res = max(res,mx[i] - mx[sta[st]]);
		while(st<ed && mx[i]<=mx[sta[ed-1]])
			ed--;
		sta[ed++] = i;
	}
	return res;
}

ll getRes(const vector<ll>& vv,int step){
	ll mod = step % vv.size();ll kk=step / vv.size();
	ll sum = 0;
	for(int i=0;i<vv.size();i++)
		sum+=vv[i];
	ll mx1 = solve(vv,mod); 	 //②m%sz≠0 
	ll mx2 = solve(vv,vv.size());//①m%sz==0 
	mx1 += max(0LL,sum) * kk;
	mx2 += max(0LL,sum) * ((kk>2)?kk-1:0);
	return max(mx1,mx2);
}
int main(){
	int T;
	scanf("%d",&T);
	int cas=1;
	while(cas<=T)
	{
		memset(vis,0,sizeof(vis));
		scanf("%d %lld %d %d",&n,&s,&m,&k);
		for(int i=0;i<n;i++)
			scanf("%lld",&v[i]);
		cnt = 0;
		MAX = 0;
		for(int i=0;i<n;i++){ 	 
			g[cnt].clear();		//清零 
			if(!vis[i]){		//枚举起点
				vis[i]=1;		//起点标记 
				g[cnt].push_back(v[i]);	//起点压入vec 
				for(int j=(i+k)%n;j!=i && !vis[j];j=(j+k)%n){//暴力枚举找循环节 
					g[cnt].push_back(v[j]);//暴力压入循环节 
					vis[j]=1;				//标记 
				}
				MAX = max(MAX,getRes(g[cnt],m));
				cnt++;
			}
		}
		if(MAX>=s) MAX=0;
		else MAX=s-MAX;
		printf("Case #%d: %lld\n", cas++, MAX);
	} 
	
}

找到相同的数距离大于x的序列

如何找到给出一堆数,你要做的就是重排后找到一个序列,使得其两个相同的数的距离要大于等于x,x就是传入的参数。比如 1 1 1 2 3 4 5 假如x=2,可以变成:1 5 4 1 3 2 1 每个1距离都为2。

做法很简单:就是先加入一个数,然后未来x个数里面都不考虑这个数,在第x+1个数里面,再看看这个数的使用次数是否为零,不为零那重新考虑这个数。 不考虑的话那就用一个优先队列去维护(谁的个数多谁贪心先放,如果不用优先队列,直接放,那么会忽略情况:比如n = 10 a=[1 1 1 1 4 4 3 3 2 2]),priority_queue里面是一个pair,第一位是个数,第二位是对应的数字,不重新考虑就直接pop,重新考虑就重新push。

bool check(int x){
	rep(i,1,n) cnt[i] = a[i];
	priority_queue<pii> Q;
	rep(i,1,n) 
		if(cnt[i]) Q.push(mp(cnt[i],i));
	
	vector<int> b;
	
	rep(i,0,n-1){
		if(i>x && cnt[b[i-x-1]]) Q.push(mp(cnt[b[i-x-1]],b[i-x-1]));
		if(Q.size()==0) {
			return false;
		}
		cnt[Q.top().Y]--; 
		b.pb(Q.top().Y);
		Q.pop();
	}	
	
	//print(b);
	//wln(x);
	return true;
}

树的平衡点

删除某个点后,组成的森林中最大的子树的节点个数最少 的那个点。

直接找子树大小即可。F[i]将i点删除之后最大连通块的大小,确定状态转移方程:

F[i] = max( n - tot[i], max(tot[k]))

K 是 i 的儿子

tot[i]是以i为根的子树的大小

DFS判拓扑序,有向图判环

vector<int> ord;
vector<int> used;
vector<vector<int>> g;

void dfs(int v)
{
	used[v] = 1;
	for(auto to:g[v]){
		if(!used[to]) dfs(to);
	}
	ord.push_back(v);
}
void solve(){
    used = vector<int>(n);
	for(int i=0;i<n;i++){
		if(!used[i]) dfs(i);
	}
    
    vector<int> pos(n);
	reverse(ord.begin(),ord.end()); //ord模拟栈操作,所以最后需要逆序
    
	for(int i = 0;i<n;i++){
		pos[ord[i]] = i;
	}
    bool bad = false;
	for(int i=0;i<n;i++){
		for(auto j:g[i]){
			if(pos[i]>pos[j]) bad=true;
		}
	}
    if(bad)  wln("-1"); //if bad==true 那么就是有环
    
}

Hopcroft-Karp 算法 求二分图最大匹配 复杂度:N根号E

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<string>
#include<vector>
#include<queue>
using namespace std;
#define ll long long

const int maxn = 50000;


int nx,ny;               //左右端点的数量
vector<int> v[maxn+10];  //实现邻接表;v[i]表示与左端点i相连的右边的点
int mx[maxn+10],my[maxn+10];   //mx表示左端点的归属  my表示右端点的归属
int dx[maxn+10],dy[maxn+10];   //dx表示左端点的深度  dy表示右端点的深度
bool pd[maxn+10];

bool find(int now)   //从左端点u出发找增广路
{
    for(int i=0;i<v[now].size();i++)
    {
        int id=v[now][i];
        if(!pd[id]&&dy[id]==dx[now]+1)
        {
            pd[id]=true;
            if(my[id]==0||find(my[id]))   //id没归属或者id的归属可以调整
            {
                mx[now]=id;
                my[id]=now;
                return true;
            }
        }
    }
    return false;
}

int hk()
{
    memset(mx,0,sizeof(mx));
    memset(my,0,sizeof(my));
    int ans=0;
    while(true)
    {
        bool swt=false;
        queue<int> q;
        memset(dx,0,sizeof(dx));
        memset(dy,0,sizeof(dy));
        for(int i=1;i<=nx;i++)     //没归属的都进队列
          if(mx[i]==0)
            q.push(i);
        while(!q.empty())
        {
            int now=q.front();
            q.pop();
            for(int i=0;i<v[now].size(); i++)
            {
                int id=v[now][i];
                if(dy[id]==0)
                {
                    dy[id]=dx[now]+1;
                    if(my[id])
                    {
                        dx[my[id]]=dy[id]+1;
                        q.push(my[id]);
                    }
                    else
                    {
                        swt=true;  //至少找到一条增广路
                    }
                }
            }
        }
        if(swt==false) break;
        memset(pd,0,sizeof(pd));
        for(int i=1;i<= nx;i++)
            if(!mx[i]&&find(i))
              ans++;
    }
    return ans;
}
int main()
{
    int n,m,e;
    cin>>n>>m>>e;
    nx=n;ny=m;
    for(int i=1;i<=e;i++)
    {
        int x,y;
        cin>>x>>y;
        v[x].push_back(y);
    }
    cout<<hk();
    return 0;
}

两遍BFS 树的最长链


int pre[200050]; //用来记录路径,输出最长链

int get_root(int x)//  从x出发找到最远的点并返回
{
	queue<pair<int,int>> Q;
	Q.push(make_pair(x,1));
	memset(vis,false,sizeof(vis));
	vis[x]=true;
	max_deep = 0;
	int root=0;
	pre[x] = x;
	while(!Q.empty()){
		int x = Q.front().first;
		int deep = Q.front().second;
		if(deep>max_deep){
			max_deep = deep;
			root = x;
		}
		Q.pop();
		for(auto i:G[x]){
			if(!vis[i]/* && !(x==delx && i==dely || x==dely && i==delx)*/){ //注释这里是把某一段给截断的意思。delxdely 是删除的那个线段,并不是删结点。
				vis[i]=true;
				pre[i] = x;
				Q.push(make_pair(i,deep+1));
			}
		}
	}
	return root;
}

int link[200050];
int len=0;
void dfs(int x){
	len = 0;
	link[len++] = x;
	while(pre[x]!=x){
		x = pre[x];
		link[len++] = x;
	}
}

初始化板子

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define X first
#define Y second

#define pb push_back
#define mp make_pair
#define rush() int TT;cin>>TT;while(TT--)
#define pii pair<int,int>
#define vi vector<int>
#define vll vector<ll>
#define vpi vector<pii>
#define vpll vector<pll>

#define all(c) begin(c), end(c)
#define lb(c, x) distance((c).begin(), lower_bound(all(c), (x)))
#define ub(c, x) distance((c).begin(), upper_bound(all(c), (x)))
#define UNIQUE(x) sort(all(x)), x.erase(unique(all(x)), x.end())

#define rep0(i,n)   for(int i=0;i<n;i++)
#define rep(i,l,r)  for(int i=l;i<=r;i++)
#define For(i,l,r)  for(int i=l;i<=r;i++)
#define FOR(i,l,r)  for(int i=l;i<=r;i++)
#define rrep(i,l,r) for(int i=l;i>=r;i--)

#define wln(x) cout<<x<<"\n"
#define w(x) cout<<x;
#define wsp() cout<<" "

#define mem(a,b) memset(a,b,sizeof(a));
#define retrun return

#define endl "\n"

#define DEBUG false

#define dwln(x) if(DEBUG) cout<<"DEBUG "<<#x<<": "<<x<<"\n"

#define IOS() {std::ios::sync_with_stdio(false); /*cin.tie(NULL);*/}


void print(vector<ll > a){			//print Vii
	for(int i=0;i<a.size();i++)
		cout<<a[i]<<" \n"[i==a.size()-1];
}
void print(vector<int> a){			//print Vii
	for(int i=0;i<a.size();i++)
		cout<<a[i]<<" \n"[i==a.size()-1];
}
void print(vector<pii> a){			//print Vpii
	int cnt=1;
	for(auto i:a)
		cout<<cnt++<<": {"<<i.X<<","<<i.Y<<"}\n";
}
void print(int a[],int l,int r){	//print a int
	for(int i=l;i<=r;i++)
		cout<<a[i]<<" \n"[i==r];
}
void print(ll  a[],int l,int r){	//print a ll
	for(int i=l;i<=r;i++)
		cout<<a[i]<<" \n"[i==r];
}

vector<pii> getDiv(int a[],int l,int r) //Divide the array a in some way 
{
	vector<pii> ret;
	rep(i,l,r)
	{
		int j=i;
		while(j+1<=r && a[j]==a[j+1]) j++;
		int len = j-i+1;
		ret.pb(mp(a[i],len));
		i=j;
	}
	return ret;
}


vector<int> getPrimeInt(int Max_prime)
{
	vector<int> ret;
	vector<bool> primes(Max_prime+5,true);
	primes[0] = primes[1] = false;
	rep(i,2,Max_prime)
	{
		if(primes[i])
		{
			ret.pb(i); 
			for(int j=2;i*j<=Max_prime;j++)
				primes[i*j]=false;
		}
	}
	return ret;
}

vector<bool> getPrimeBool(int Max_prime)
{
	
	vector<int> ret;
	vector<bool> primes(Max_prime+5,true);
	primes[0] = primes[1] = false;
	rep(i,2,Max_prime)
	{
		if(primes[i])
		{
			ret.pb(i); 
			for(int j=2;i*j<=Max_prime;j++)
				primes[i*j]=false;
		}
	}
	return primes;
}

vector<int> prime_sieve(const int N, const int Q = 17, const int L = 1 << 15) {
    using u8 = unsigned char;
    static const int rs[] = {1, 7, 11, 13, 17, 19, 23, 29};
    struct P {
        P(int p) : p(p) {}
        int p;
        int pos[8];
    };
    auto approx_prime_count = [](const int N) -> int { return N > 60184 ? N / (log(N) - 1.1) : max(1., N / (log(N) - 1.11)) + 1; };
 
    const int v = sqrt(N), vv = sqrt(v);
    vector<bool> isp(v + 1, true);
    for(int i = 2; i <= vv; ++i)
        if(isp[i]) {
            for(int j = i * i; j <= v; j += i) isp[j] = false;
        }
 
    const int rsize = approx_prime_count(N + 30);
    vector<int> primes = {2, 3, 5};
    int psize = 3;
    primes.resize(rsize);
 
    vector<P> sprimes;
    size_t pbeg = 0;
    int prod = 1;
    for(int p = 7; p <= v; ++p) {
        if(!isp[p]) continue;
        if(p <= Q) prod *= p, ++pbeg, primes[psize++] = p;
        auto pp = P(p);
        for(int t = 0; t < 8; ++t) {
            int j = (p <= Q) ? p : p * p;
            while(j % 30 != rs[t]) j += p << 1;
            pp.pos[t] = j / 30;
        }
        sprimes.push_back(pp);
    }
 
    vector<u8> pre(prod, 0xFF);
    for(size_t pi = 0; pi < pbeg; ++pi) {
        auto pp = sprimes[pi];
        const int p = pp.p;
        for(int t = 0; t < 8; ++t) {
            const u8 m = ~(1 << t);
            for(int i = pp.pos[t]; i < prod; i += p) pre[i] &= m;
        }
    }
 
    const int block_size = (L + prod - 1) / prod * prod;
    vector<u8> block(block_size);
    u8 *pblock = block.data();
    const int M = (N + 29) / 30;
 
    for(int beg = 0; beg < M; beg += block_size, pblock -= block_size) {
        int end = min(M, beg + block_size);
        for(int i = beg; i < end; i += prod) { copy(pre.begin(), pre.end(), pblock + i); }
        if(beg == 0) pblock[0] &= 0xFE;
        for(size_t pi = pbeg; pi < sprimes.size(); ++pi) {
            auto &pp = sprimes[pi];
            const int p = pp.p;
            for(int t = 0; t < 8; ++t) {
                int i = pp.pos[t];
                const u8 m = ~(1 << t);
                for(; i < end; i += p) pblock[i] &= m;
                pp.pos[t] = i;
            }
        }
        for(int i = beg; i < end; ++i) {
            for(int m = pblock[i]; m > 0; m &= m - 1) { primes[psize++] = i * 30 + rs[__builtin_ctz(m)]; }
        }
    }
    assert(psize <= rsize);
    while(psize > 0 && primes[psize - 1] > N) --psize;
    primes.resize(psize);
    return primes;
}

ll qpow(ll x,ll y)
{
	ll temp=x;
	ll ans=1;
	while(y){
		if(y&1) ans=(ans*temp);
		temp=(temp*temp);
		y>>=1;
	}
	return ans;
}

ll qpow(ll x,ll y,ll MOD)
{
	ll temp=x;
	ll ans=1;
	while(y){
		if(y&1) ans=(ans*temp)%MOD;
		temp=(temp*temp)%MOD;
		y>>=1;
	}
	return ans;
}


vector<int> primes;
void INIT()
{
//	primes = getPrimeInt(1e6);       	//获得素数vector 
	//vector<bool> primes = getPrimeBool(1e6);  	//获得素数的bool数组
	/*auto P = prime_sieve(1000000);
		ll d;
		cin>>d;
	    ll ans = 1;
	    ll t = P[lb(P, 1 + d)];
	    ans *= t;
	    t = P[lb(P, t + d)];
	    ans *= t;
	    cout << ans << endl;*/
}

void solve()
{
	
}

int main()
{
	IOS();
	INIT();
	rush()
	{
		solve();
	}
    
	return 0;
}



/*
ATTENTION:
1.记得初始化数组
2.0和1是很多问题解决的关键
3.别心急,坚信你不会的,别人也很难做出来
4.不要高估自己
5.不要自以为是
6.不要以为自己所训练一定会派上用场
7.想找可爱小姐姐撒娇
*/


posted @ 2021-09-05 15:07  my-island  阅读(656)  评论(0)    收藏  举报