2023年8月初补题

看这个人的专栏

https://blog.csdn.net/qq_42555009/category_8770183.html

有一定思维难度,贪心,用multiset实现

翻译:

链接:https://ac.nowcoder.com/acm/contest/33187/H
来源:牛客网

在夜之城的中心有一座高大的建筑,叫做Arasaka塔。每天都有人在Arasaka塔里上上下下。

Arasaka塔有k层。今天,有n个人要乘电梯,第i个人想从ai楼层去到bi楼层。电梯一次最多可以搭载m个人,并且它从一楼开始。电梯上升或下降一层需要1单位时间。人们进出电梯的时间可以忽略不计。

然而,今天电梯有些问题。电梯的运行方向不能再任意改变。当电梯正在下行时,只有当它到达一楼时才能改变其运行方向。

将所有人送到他们的目的地并让电梯回到一楼需要的最少时间是多少?

拓展,如果是某个批次,人数,都不一样怎么办。

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=2e5+10;
struct node{
    int l,r,op;
    bool operator<(const node&b)const{
        return r<b.r;
    }
}a[N];
int n,m,k;
typedef pair<int,int>PII;
multiset<PII>S[2];
signed main(){
    ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
    cin>>n>>m>>k;
    for(int i=1;i<=n;i++){
        int l,r;
        cin>>l>>r;
        if(l<r)a[i]={l,r,0};
        else a[i]={r,l,1};
    }
    sort(a+1,a+1+n);
    int ans=0;
    for(int i=n;i>=1;i--){
        int l=a[i].l,r=a[i].r,op=a[i].op;
        auto it=S[op].lower_bound({r,1});
        if(it==S[op].end()){
            ans+=2*(r-1);
            S[0].insert({r,m});
            S[1].insert({r,m});
        }
        it=S[op].lower_bound({r,1});
//         int room=it->second;
        int room=(*it).second;
        S[op].erase(it);
        if(--room)S[op].insert({r,room});
        S[op].insert({l,1});
    }
    cout<<ans;
}

矩阵技巧

链接:https://ac.nowcoder.com/acm/contest/33187/I
来源:牛客网

在一个名为江湖的虚拟世界里,有 n 名拥有独特技能的武术大师。然而,由于缺乏锻炼,他们不再是熟练的大师,甚至开始发胖!为了解决这个问题,他们决定举办一个名为 “让脂肪紧张” (LFT, IF: 0.053) 的武术大会,他们可以在其中深入学习,以提升他们的技能。

第 i 名武术大师有两个属性,即能力属性和技能属性。第 i 名大师的能力属性可以用长度为 k 的向量 Xi 表示,他的技能属性可以用长度为 d 的向量 Yi 表示。

在武术大会上,一个大师会深入学习其他每一个武术大师以提升他们的技能。

定义第 i 名大师和第 j 名大师之间的学习效率为 le(i,j),即他们的能力属性 Xi 和 Xj 之间的余弦相似度。正式地,我们有:

le(i,j) = Xi * Xj / (||Xi|| * ||Xj||)

其中 “*” 表示两个向量之间的点积,“|| ||” 表示向量的大小。注意,le(i,i) = 1 总是成立。

在深入学习其他人后,第 i 名大师的技能属性将变为:

Yi_new = Σ(j=1 to n) le(i,j) * Yj

n 名武术大师的学习过程是同时发生的。

请计算所有武术大师的新技能属性。在这里插入图片描述

#include <bits/stdc++.h>
using namespace std;
vector<vector<double>> operator*(vector<vector<double>> a, vector<vector<double>> b)
{
    int n = a.size(), m = a[0].size(), k = b[0].size();
    vector<vector<double>> ans(n, vector<double>(k, 0));
    for (int i = 0; i < n;i++)
        for (int j = 0; j < k;j++)
            for (int l = 0; l < m;l++)
                ans[i][j] += a[i][l] * b[l][j];
    return ans;
}
int main()
{
    int n, k, d;
    scanf("%d%d%d", &n, &k, &d);
    vector<vector<double>> a(n, vector<double>(k)), b(k, vector<double>(n)), c(n, vector<double>(d));
    for (int i = 0; i < n;i++)
    {
        double all = 0;
        for (int j = 0; j < k;j++)
        {
            scanf("%lf", &a[i][j]);
            all += a[i][j] * a[i][j];
        }
        all = sqrt(all);
        for (int j = 0; j < k; j++)
        {
            a[i][j] /= all;
            b[j][i] = a[i][j];
        }
    }
    for (int i = 0; i < n;i++)
        for (int j = 0; j < d;j++)
            scanf("%lf", &c[i][j]);
    auto ans = a * (b * c);
    for(auto i:ans)
    {
        for (auto j : i)
            printf("%.10lf ", j);
        printf("\n");
    }
    return 0;
}

二分+spfa,但是会爆炸爆出double 需要用到log取对数运算

链接:https://ac.nowcoder.com/acm/contest/33187/D
来源:牛客网

Link正在开发一款游戏。在这款游戏中,玩家可以使用各种类型的资源来制作物品,而制作出的物品也可以用来制作其他物品。

正式地说,游戏中有 n 种物品,编号从 1 到 n,并有 m 个配方。在第 i 个配方中,玩家可以使用 k * ai 个第 bi 类物品来制作 k * ci 个第 di 类物品,其中 k 可以是任何正实数。

有一天,他发现一名玩家拥有超过 18,446,744,073,709,551,615 个相同的物品,这导致了服务器崩溃。显然,不使用漏洞这是不可能的。

Link很快发现制作配方中有问题。通过反复制作一些特殊物品,玩家可能会获得无限的资源!

Link不想逐个调整配方,所以他简单地添加了一个参数 w。现在,玩家可以使用 k * ai 个第 bi 类物品来制作 w * k * ci 个第 di 类物品。

Link想知道:他可以设置的最大的 w 是多少,以便没有玩家可以通过反复制作物品获得无限的物品?

#include<bits/stdc++.h>
using namespace std;
const int N=1010,M=2020;
// #define double long double
const double eps=1e-10;
int h[N],e[M],ne[M],n,m,idx;
double w[M];
void add(int a,int b,double c){
    e[idx]=b,w[idx]=c,ne[idx]=h[a],h[a]=idx++;
}
int st[N];
double d[N];
int vis[N],cnt[N];
int spfa(int uu,double x){
    queue<int>q;
    q.push(uu);
    d[uu]=1;
    vis[uu]=1;
    while(q.size()){
        int u=q.front();
        q.pop();
        vis[u]=0;
        st[u]=1;
        for(int i=h[u];~i;i=ne[i]){
            int j=e[i];
            if(d[j]<d[u]+w[i]+x){
                d[j]=d[u]+w[i]+x;
                if(!vis[j]){
                    vis[j]=1,q.push(j);
                    if(++cnt[j]>=n)return 1;
                }
            }
        }
//         if(d[uu]>1)return 1;
    }
    return 0;
}
int check(double mid){
    for(int i=1;i<=n;i++)st[i]=0,d[i]=0,vis[i]=0,cnt[i]=0;
    for(int i=1;i<=n;i++){
        if(!st[i]){
            st[i]=1;
            if(spfa(i,mid))return 1;
        }
    }
    return 0;
}

signed main(){
    ios::sync_with_stdio(0);cin.tie(0);
    cin>>n>>m;
    memset(h,-1,sizeof h);
    for(int i=1;i<=m;i++){
        int a,b,c,d;
        cin>>a>>b>>c>>d;
        add(b,d,log((double)c/a));
    }
    
    double l=0.0,r=1.0;
    while(r-l>eps){
        double mid=(l+r)/2;
        if(check(log(mid)))r=mid;
        else l=mid;
    }
    cout<<fixed<<setprecision(12)<<r;
}

随机化思想+状压dp

在这里插入图片描述

#include<bits/stdc++.h>
#define LL long long

using namespace std;


const int MXN=5005;
struct Egde{
	int v,val,nxt;
}e[MXN*2];

int n,k,cnt;
int head[MXN],a[MXN],col[MXN];
LL ans,dp[MXN][(1<<6)];

void add(int v,int u,int w){
	e[++cnt].v=v;
	e[cnt].val=w;
	e[cnt].nxt=head[u];
	head[u]=cnt;
}

void dfs(int u,int fa){
	int v,w;
	for(int S=0;S<(1<<k);S++) dp[u][S]=-1e17;
	dp[u][0]=0;
	dp[u][(1<<col[a[u]])]=0;
	for(int i=head[u];i;i=e[i].nxt){
		v=e[i].v; w=e[i].val;
		if(v==fa) continue;
		dfs(v,u);
		
		for(int S=1;S<(1<<k);S++) dp[v][S]+=w;
		for(int S=(1<<k)-1;S>=0;S--){
			for(int T=S;T;T=(T-1)&S){
				dp[u][S]=max(dp[u][S],dp[u][S-T]+dp[v][T]);
				if(S-T) ans=max(ans,dp[u][S-T]+dp[v][T]);
			}
		}
	}
}

int main(){
	srand((int)time(0));
	int T,u,v,w;
	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%d%d",&u,&v,&w);
		add(u,v,w); add(v,u,w);
	}
	T=200;
	while(T--){
		for(int i=1;i<=n;i++) {
			col[i]=rand()%k;
		}
		dfs(1,0); 
	}
	printf("%lld\n",ans);
}

鸽巢原理??

在这里插入图片描述

#include <bits/stdc++.h>
#define fp(i, a, b) for (int i = a, i##_ = (b) + 1; i < i##_; ++i)
#define fd(i, a, b) for (int i = a, i##_ = (b) - 1; i > i##_; --i)

using namespace std;
const int N = 2e7 + 5;
using pii = pair<int, int>;
int n, m;
pii w[N];
unordered_map<int, vector<int>> A, B;
void Solve() {   
    scanf("%d%d", &n, &m);
    for (int x, i = 1; i <= n; ++i) scanf("%d", &x), A[x].push_back(i);
    for (int x, i = 1; i <= m; ++i) scanf("%d", &x), B[x].push_back(i);
    vector<pii> a, b;
    pii ma, mb;
    for (auto &[i, v] : A) {
        a.push_back({i, v[0]});
        if (v.size() > 1) ma = {v[0], v[1]};
    }
    for (auto &[i, v] : B) {
        b.push_back({i, v[0]});
        if (v.size() > 1) mb = {v[0], v[1]};
    }
    if (ma.first && mb.first)
        return printf("%d %d %d %d\n", ma.first, ma.second, mb.first, mb.second), void();
    b.resi***(b.size(), size_t(2e7 / a.size())));
    for (auto &[x, i] : a)
        for (auto &[y, k] : b) {
        auto [j, l] = w[x + y];
        if (j && i != j && k != l) {
            printf("%d %d %d %d\n", i, j, k, l);
            return;
        } else w[x + y] = {i, k};
    }
    puts("-1");
}
int main() {
    int t = 1;
    while (t--) Solve();
    return 0;
}

bfs

在这里插入图片描述
终点是一个必胜态,从终点出发去bfs,把必胜态丢尽队列去更新
如果有两条边连接着必胜态,必胜,如果这个点有两个以上被必胜态访问过,也必胜

#include <bits/stdc++.h>
using namespace std;
int main()
{
    int caset, n, m, s, t;
    scanf("%d", &caset);
    while (caset--)
    {
        scanf("%d%d%d%d", &n, &m, &s, &t);
        vector<vector<int>> deg(n + 1, vector<int>(n + 1, 0));
        vector<vector<int>> g(n + 1);
        for (int i = 1, u, v; i <= m; i++)
        {
            scanf("%d%d", &u, &v);
            deg[u][v]++;
            deg[v][u]++;
            g[u].push_back(v);
            g[v].push_back(u);
        }
        vector<int> vis(n + 1), in(n + 1, 0);
        queue<int> q;
        vis[t] = 1;
        q.push(t);
        while (!q.empty())
        {
            int tp = q.front();
            q.pop();
            for (auto i : g[tp])
            {
                if (deg[tp][i] > 1)
                {
                    if (!vis[i] && vis[tp])
                    {
                        vis[i] = 1;
                        q.push(i);
                    }
                }
                else if (deg[tp][i] == 1 && vis[tp])
                    in[i]++;
                if (in[i] >= 2 && !vis[i])
                {
                    vis[i] = 1;
                    q.push(i);
                }
            }
        }
        if (vis[s])
            printf("Join Player\n");
        else
            printf("Cut Player\n");
    }
    return 0;
}

二分,差分转前缀和,前缀和转差分

链接:https://ac.nowcoder.com/acm/contest/33195/B
来源:牛客网

在著名的游戏"Fall Guys: Ultimate Knockout"中,有一个叫做"Perfect Match"的关卡。我们将游戏的规则简化如下:你站在一个n×n的网格上,有m种水果。而且,每个网格上都标记有某种水果。保证每种水果在至少1个网格上出现,最多在20个网格上出现。你可以选择一开始就站在任何一个网格上,然后游戏中的大屏幕上会出现一种水果,你需要在倒计时结束之前跑到一个标记着和屏幕上相同水果的网格上。在游戏中,你只能横向或纵向移动,即如果你站在(x1,y1)并移动到(x2,y2),你移动的距离是|x1-x2| + |y1-y2|。
你想选择一个格子作为初始站立的位置,以使在最坏情况下(即所有可能的m种水果都出现在大屏幕上时)你需要移动的距离最小,这样即使在最坏情况下你也有希望不被淘汰!计算这个最小化的距离。

在这里插入图片描述

#include<bits/stdc++.h>
using namespace std;
const int N=2010;
int n,m,a[N][N];
typedef pair<int,int>PII;
vector<PII>p[N*N];
int s[N][N],v[N][N];
int X[N*N],Y[N*N],x,y;
int ok[N][N];
int check(int d){
    memset(v,0,sizeof v);
    for(int c=1;c<=m;c++){
        x=0,y=0;
        X[x++]=0;
        Y[y++]=0;
        for(auto [i,j]:p[c]){
            int x1=max(1,i-d);X[x++]=x1;
            int y1=max(1,j-d);Y[y++]=y1;
            int x2=min(2*n+1,i+d+1);X[x++]=x2;
            int y2=min(2*n+1,j+d+1);Y[y++]=y2;
        }
        sort(X,X+x),sort(Y,Y+y);
        x=unique(X,X+x)-X,y=unique(Y,Y+y)-Y;
        for(int i=0;i<x;i++)for(int j=0;j<y;j++)s[i][j]=0;
        for(auto [i,j]:p[c]){
            int x1=lower_bound(X,X+x,max(1,i-d))-X;
            int y1=lower_bound(Y,Y+y,max(1,j-d))-Y;
            int x2=lower_bound(X,X+x,min(2*n+1,i+d+1))-X;
            int y2=lower_bound(Y,Y+y,min(2*n+1,j+d+1))-Y;
            s[x1][y1]++;
            s[x1][y2]--;
            s[x2][y1]--;
            s[x2][y2]++;
        }
        for(int i=1;i<x;i++){
            for(int j=1;j<y;j++){
                s[i][j]+=s[i-1][j]+s[i][j-1]-s[i-1][j-1];
            }
        }
        for(int i=1;i<x;i++){
            for(int j=1;j<y;j++)s[i][j]=s[i][j]>0;
        }
        for(int i=1;i<x;i++){
            for(int j=1;j<y;j++){
                v[X[i]][Y[j]]+=s[i][j]-s[i-1][j]-s[i][j-1]+s[i-1][j-1];
            }
        }
    }
    
    for(int i=1;i<=2*n;i++){
        for(int j=1;j<=2*n;j++){
            v[i][j]+=v[i-1][j]+v[i][j-1]-v[i-1][j-1];
            if(ok[i][j]&&v[i][j]==m)return 1;
        }
    }
    return 0;
}
signed main(){
    ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
    cin>>n>>m;
    for(int i=1;i<=n;i++)for(int j=1;j<=n;j++){
        cin>>a[i][j];
        ok[i+j][i-j+n]=1;
        p[a[i][j]].push_back({i+j,i-j+n});
    }
    int l=0,r=n;
    while(l<r){
        int mid=l+r>>1;
        if(check(mid))r=mid;
        else l=mid+1;
    }
    cout<<r;
}

费用流

在这里插入图片描述
有些题它明明靠的是网络流,但是需要满足特定的需求,我们可以利用费用流的特点去解决

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10,M=1e6+10;
int n,m,S,T,h[N],e[M],ne[M],f[M],w[M];
int incf[N],pre[N],d[N],st[N],idx,q[N];
void add(int a,int b,int c,int d){
    e[idx]=b,f[idx]=c,w[idx]=d,ne[idx]=h[a],h[a]=idx++;
    e[idx]=a,f[idx]=0,w[idx]=-d,ne[idx]=h[b],h[b]=idx++;
}
bool spfa(){
    memset(d,-0x3f,sizeof d);
    memset(incf,0,sizeof incf);
    int hh=0,tt=1;
    q[0]=S,d[S]=0,incf[S]=1e9;
    while(hh!=tt){
        int t=q[hh++];
        if(hh==N)hh=0;
        st[t]=0;
        for(int i=h[t];~i;i=ne[i]){
            int ver=e[i];
            if(f[i]&&d[ver]<d[t]+w[i]){
                d[ver]=d[t]+w[i];
                pre[ver]=i;
                incf[ver]=min(incf[t],f[i]);
                if(!st[ver]){
                    st[ver]=1;
                    q[tt++]=ver;
                    if(tt==N)tt=0;
                    
                }
            }
        }
    }
    return incf[T]>0;
}
void EK(int &flow,int &cost){
    flow=cost=0;
    while(spfa()){
        int t=incf[T];
        flow+=t;
        cost+=t*d[T];
        for(int i=T;i!=S;i=e[pre[i]^1]){
            f[pre[i]]-=t;
            f[pre[i]^1]+=t;
        }
    }
}

signed main(){
    ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
    memset(h,-1,sizeof h);
//     cin>>n>>m>>S>>T;
//     while(m--){
//         int a,b,c,d;
//         cin>>a>>b>>c>>d;
//         add(a,b,c,d);
//     }
//     int flow,cost;
//     EK(flow,cost);
//     cout<<flow<<" "<<cost;
    cin>>n>>m;
    S=0,T=N-1;
    for(int i=1;i<=n;i++)add(S,i,1,0);
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            char c;
            cin>>c;
            if(c=='1')add(i,j+n,1,0);
        }
    }
    
    for(int j=1;j<=m;j++){
        for(int x=1;x<=n;x++)add(j+n,T,1,x);
    }
    int flow,cost;
    EK(flow,cost);
//     cout<<"flow="<<flow<<" cost="<<cost<<endl;
    if(flow!=n){
        cout<<-1;
        return 0;
    }
    for(int u=1;u<=n;u++){
        for(int i=h[u];~i;i=ne[i]){
            int j=e[i];
            if(!f[i]&&j>n){
                cout<<j-n<<" ";
                break;
            }
        }
    }
}

决策单调性

在这里插入图片描述

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=1e5+10;
int n,m;
struct node{
    int w,q;
    bool operator<(const node&t)const{
        return w*(10000-t.q)<t.w*(10000-q);
    }
}a[N];
double f[21];
signed main(){
    ios::sync_with_stdio(0);cin.tie(0);
    cin>>n>>m;
    for(int i=1;i<=n;i++)cin>>a[i].w;
    for(int i=1;i<=n;i++)cin>>a[i].q;
    sort(a+1,a+1+n);
    for(int j=1;j<=m;j++)f[j]=-1e9;
    f[0]=0;
    for(int i=1;i<=n;i++){
        for(int j=m;j>=1;j--)
        f[j]=max(f[j],f[j-1]*a[i].q/10000+a[i].w);
    }
    cout<<fixed<<setprecision(10)<<f[m];
}

2020CCPC长春K题启发式合并

难度主要是那个放缩,理解了放缩的话,
就可以把可能的答案存进vector< int >g[x]里面,表示x的可能的答案
以后的题目可能换汤不换药,主要是找到怎么放缩式子或者快速找到可能的答案,只要是gcd都可以往这方面去考虑

题解传送门
题目传送门

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=3e5+10;
int n,m,p[N],sz[N],a[N];
ll ans;
vector<int>g[N];//g[i]表示i可能的答案,枚举i的约数存入进去
unordered_map<int,int>mp[N];
int find(int x){
    if(p[x]==x)return x;
    return p[x]=find(p[x]);
}
// a^b==gcd(a,b)有多少对
//a^b>=|a-b|>=gcd(a,b)


void merge(int x,int y){
    x=find(x),y=find(y);
    if(x==y)return;
    if(sz[x]<sz[y])swap(x,y);
    for(auto [k,v]:mp[y]){
        for(auto t:g[k])if(mp[x].count(t))ans+=(ll)mp[x][t]*v;
    }
    for(auto [k,v]:mp[y])mp[x][k]+=v;
    mp[y].clear();
    p[y]=x;
    sz[x]+=sz[y];
}
void so(int x){
    for(int d=1;d*d<=x;d++){
        if(x%d)continue;
        int i=d,j=x/i,y;
        y=x-i;if((x^y)==__gcd(x,y)&&y>0)g[x].push_back(y);
        y=x+i;if((x^y)==__gcd(x,y)&&y<=2e5)g[x].push_back(y);
        if(i==j)continue;
        y=x-j;if((x^y)==__gcd(x,y)&&y>0)g[x].push_back(y);
        y=x+j;if((x^y)==__gcd(x,y)&&y<=2e5)g[x].push_back(y);
    }
}
signed main(){
    ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
    for(int i=1;i<=2e5;i++)so(i);
    /*for (int i = 1; i <= 200000; i++) {
		for (int j = i + i; j <= 200000; j += i) {
			if (gcd(j, i^j) == i)g[j].push_back(i^j);
		}
	}*/
    cin>>n>>m;
    for(int i=1;i<=n+m;i++)p[i]=i,sz[i]=1;
    for(int i=1;i<=n;i++)cin>>a[i],mp[i][a[i]]++;
    while(m--){
        int op,x;
        cin>>op;
        if(op==1){int v;cin>>x>>v;a[x]=v;mp[x][v]=1;}
        if(op==2){int y;cin>>x>>y;merge(x,y);}
        if(op==3){
            int v;cin>>x>>v;
            int u=find(x);
            for(auto t:g[a[x]])if(mp[u].count(t))ans-=mp[u][t];
            mp[u][a[x]]--;
            a[x]=v;
            for(auto t:g[a[x]])if(mp[u].count(t))ans+=mp[u][t];
            mp[u][a[x]]++;
        }
        cout<<ans<<'\n';
    }
}

2020CCPC长春F. Strange Memory(长链剖分/启发式合并)

https://codeforces.com/gym/102832/problem/F

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
int a[N],n,x,m;
long long ans;
int dfn[N],L[N],R[N],o,u,v,sz[N],son[N];
vector<int>g[N];
int f[N*11][20][2];
//考虑每个节点的贡献,枚举子树
//f[i][j][k]表示权值为i第j为k的个数
void dfs(int u,int fa=0){
    sz[u]=1;
    dfn[++o]=u;
    L[u]=o;
    for(auto j:g[u]){
        if(j==fa)continue;
        dfs(j,u);
        sz[u]+=sz[j];
        if(sz[j]>sz[son[u]])son[u]=j;
    }
    R[u]=o;
}
void dsu(int u,int fa=0,int op=1){
    for(auto j:g[u]){
        if(j==fa||j==son[u])continue;
        dsu(j,u,1);
    }
    if(son[u])dsu(son[u],u,0);
    for(int i=0;i<20;i++)f[a[u]][i][u>>i&1]++;
    for(auto j:g[u]){
        if(j==fa||j==son[u])continue;
        for(int k=L[j];k<=R[j];k++){
            for(int i=0;i<20;i++){
                int p=a[u]^a[dfn[k]];
                int c=dfn[k]>>i&1;
                ans+=(1ll<<i)*f[p][i][1-c];
            }
        }
        for(int k=L[j];k<=R[j];k++){
            for(int i=0;i<20;i++)f[a[dfn[k]]][i][dfn[k]>>i&1]++;
        }
    }
    if(op){
        for(int j=L[u];j<=R[u];j++){
            int p=dfn[j];
            for(int i=0;i<20;i++)f[a[p]][i][p>>i&1]--;
        }
    }
}
signed main(){
    ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
    cin>>n;
    for(int i=1;i<=n;i++)cin>>a[i];
    for(int i=1;i<n;i++){
        cin>>u>>v;
        g[u].push_back(v);
        g[v].push_back(u);
    }
    dfs(1);
    dsu(1);
    cout<<ans;
}

倍增优化网络流建边(树链剖分也可以

#include<bits/stdc++.h>
using namespace std;
#define INF 1e9
const int N =(1e4+10)*21,M=N*10;
int h[N],e[M],f[M],ne[M],idx,d[N],cur[N],q[N],o;
int n,m,S,T;
void add(int a,int b,int c){
    e[idx]=b,f[idx]=c,ne[idx]=h[a],h[a]=idx++;
    e[idx]=a,f[idx]=0,ne[idx]=h[b],h[b]=idx++;
}
bool bfs()  // 创建分层图
{
    int hh = 0, tt = 0;
    memset(d, -1, sizeof d);
    q[0] = S, d[S] = 0, cur[S] = h[S];
    while (hh <= tt)
    {
        int t = q[hh ++ ];
        for (int i = h[t]; ~i; i = ne[i])
        {
            int ver = e[i];
            if (d[ver] == -1 && f[i])
            {
                d[ver] = d[t] + 1;
                cur[ver] = h[ver];
                if (ver == T) return true;
                q[ ++ tt] = ver;
            }
        }
    }
    return false;  // 没有增广路
}

int find(int u, int limit)  // 在残留网络中增广
{
    if (u == T) return limit;
    int flow = 0;
    for (int i = cur[u]; ~i && flow < limit; i = ne[i])
    {
        cur[u] = i;  // 当前弧优化
        int ver = e[i];
        if (d[ver] == d[u] + 1 && f[i])
        {
            int t = find(ver, min(f[i], limit - flow));
            if (!t) d[ver] = -1;
            f[i] -= t, f[i ^ 1] += t, flow += t;
        }
    }
    return flow;
}

int dinic()
{
    int r = 0, flow;
    while (bfs()) while (flow = find(S, INF)) r += flow;
    return r;
}
typedef pair<int,int>PII;
vector<PII>g[N];
int fa[N][20],id[N][20],dep[N];
void dfs(int u,int F=0,int ID=0){
    fa[u][0]=F;
    id[u][0]=ID;
    for(int i=1;i<20;i++){
        fa[u][i]=fa[fa[u][i-1]][i-1];
        id[u][i]=++o;
        add(id[u][i],id[u][i-1],1e9);
        add(id[u][i],id[fa[u][i-1]][i-1],1e9);
    }
    dep[u]=dep[F]+1;
    for(auto t:g[u]){
        int j=t.first;
        int ID=t.second;
        if(j==F)continue;
        dfs(j,u,ID);
    }
}
void lca(int a,int b){
    if(dep[a]<dep[b])swap(a,b);
    for(int i=19;i>=0;i--){
        if(dep[fa[a][i]]>=dep[b]){
            add(o,id[a][i],1e9);
            a=fa[a][i];
        }
    }
    if(a==b)return;
    for(int i=19;i>=0;i--){
        if(fa[a][i]!=fa[b][i]){
            add(o,id[a][i],1e9);
            add(o,id[b][i],1e9);
            a=fa[a][i],b=fa[b][i];
        }
    }
    add(o,id[a][0],1e9),add(o,id[b][0],1e9);
}
signed main(){
    ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
    memset(h,-1,sizeof h);
    cin>>n>>m;
    S=1,T=2,o=2;
    for(int i=1;i<n;i++){
        int a,b,c;
        cin>>a>>b>>c;
        add(++o,T,c);
        g[a].push_back({b,o});
        g[b].push_back({a,o});
    }
    dfs(1);
    int sum=0;
    while(m--){
        int a,b,x,y;
        cin>>a>>b>>x>>y;
        if(x<y)continue;//很关键,一定要剔除在外,不能让sum加上这个
        sum+=x-y;
        add(S,++o,x-y);
        lca(a,b);
    }
    cout<<sum-dinic();
}

二分图博弈

/*二分图博弈
给出一张二分图和起始点 H
,A和B轮流操作,每次只能选与上个被选择的点(第一回合则是点
)相邻的点,且不能选择已选择过的点,无法选点的人输掉
。一个经典的二分图博弈模型是在国际象棋棋盘上,双方轮流移动一个士兵
,不能走已经走过的格子,问谁先无路可走。

这类模型其实很好处理。考虑二分图的最大匹配,如果最大匹配一定包含
,那么先手必胜,否则先手必败。
需要注意的是,如果采用Dinic,不要根据有没有 H
点建两次图。而是在建图时把涉及 H
点的边存下来,跑完第一次Dinic后再建这些边,第二次Dinic看有没有增加流量。
或者直接源点S向start点连一条容量为1的边,再跑一次dinic,如果flow还有,也就是说flow>0先手必胜
如果flow=0,说明起始点不是关键点是可有可无的,先手必败

题目大意:给出一个密码锁,两个人一起玩游戏,给出初始的密码,规定:

每一次都可以转动一个位置的数字一个单位
不可以转动到已经出现过的数字
不可以转动到被 ban 掉的数字
无法转动的人视为失败,问谁能获胜

对于此题而言,每次将数字的某个位置转动一个单位,其数位和的奇偶性会发生变化,从这里入手,将所有数字拆分成一张二分图

建图如下:

源点 -> 奇数:流量为 1
奇数 -> 偶数:如果数位和的绝对值相差 1 个单位:流量为 1
偶数 -> 汇点:流量为 1
先不建起点跑最大流,然后把起点加上,看看有没有增广路即可

*/

#include<iostream>
#include <cstring>
#include <cmath>
#include <map>
#include <vector>
#include <algorithm>
#include <set>
#define int ll
using namespace std;
typedef long long ll;
const int M = 2e6 + 5;
const int N = 2e5 + 5;
const int INF = 0x3f3f3f3f;
int tot,len;
int fac[]={1,10,100,1000,10000,100000,1000000}; 
int vis[N];
struct node
{
    int v,w,to;
} edge[M*2];
int pre[N],cnt,dep[N];
int S,T,z,head[N],sum,id;
int n,m,q[N],cur[N];
void add(int u,int v,int w)
{
    edge[cnt]= {v,w,head[u]};
    head[u]=cnt++;
    edge[cnt]= {u,0,head[v]};
    head[v]=cnt++;
}
bool bfs()
{
    for(int i=0; i<=T; i++)
        dep[i]=0;
    dep[S]=1;
    int l=0,r=1;
    q[r]=S;
    while(l<r)
    {
        int u=q[++l];
        for(int i=head[u]; i!=-1; i=edge[i].to)
        {
            int v=edge[i].v;
            if(!dep[v]&&edge[i].w)
                dep[v]=dep[u]+1,q[++r]=v;
        }
    }
    return dep[T];
}
int dfs(int u,int mi)
{
    int res=0;
    if(mi==0||u==T)
        return mi;
    for(int &i=cur[u]; i!=-1; i=edge[i].to)
    {
        int v=edge[i].v;
        if(dep[u]+1==dep[v]&&edge[i].w)
        {
            int minn=dfs(v,min(mi-res,edge[i].w));
            edge[i].w-=minn;
            edge[i^1].w+=minn;
            res+=minn;
            if(res==mi)
                return res;
        }
    }
    if(res==0) dep[u]=0;
    return res;
}
int dinic()
{
    ll res=0;
    while(bfs())
    {
        memcpy(cur,head,sizeof(head));
//        cout<<res<<endl;
        res+=dfs(S,INF);
    }
    return res;
}
int stF;
int getnum(int i){
	int ans=0;
	//cout<<i<<" ";
	while(i){
		ans += i%10;
		i/= 10;
	}
	//cout<<ans<<endl;
	return ans;
}

void cal(int n){
	int index = 10, res,org = n;
	for(int i = 0; i<len;i++){
		res = n/fac[i];
//		n/=10;
		res%=10;
		int ub = (res+1)%10;
		int lb = (res-1+10)%10;
//		cout<<"len: "<<len<<endl;
//		cout<<org<<": "<<org-res*pow(10,i)+ub*pow(10,i)<<" "<<org-res*pow(10,i)+lb*pow(10,i)<<endl;
		if(!vis[(org-res*fac[i]+ub*fac[i])])
			add(org,org-res*fac[i]+ub*fac[i],INF);
		if(!vis[(org-res*fac[i]+lb*fac[i])])
			add(org,org-res*fac[i]+lb*fac[i],INF);
	}
}
signed main(){
	ios::sync_with_stdio(0);
	int t,st,mxflow;
	cin >> t;
	while(t--){
		memset(vis,0,sizeof vis);
		cin >> len >> n >> st;
		S = fac[len],T = S+1;
//		cout<<S<<" "<<T<<endl;
		cnt = 0;
		for(int i = 0; i<=T; i++) head[i] = -1;
		stF = getnum(st);
		stF &= 1;
		int tmp;
//		_set.insert(st);
		for(int i =0; i < n;i++){
			cin >> tmp;
			vis[tmp]=1;
		}
		for(int i = 0; i<S;i++){
			if(vis[i]) continue;
			if( (getnum(i)&1) == stF){
				cal(i);
				if(i != st) add(S,i,1);
			}else{
				add(i,T,1);	
			}
		}
		

		mxflow = dinic();

		add(S,st,1);

		mxflow=dinic();
		if(mxflow == 0) cout<<"Bob\n";
		else cout<<"Alice\n"; 
	} 
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值