ACM日常訓練日記——8.8(二分dp,最小生成樹+克魯斯卡爾演算法和普利姆演算法)

冬天的睡袋發表於2024-08-09
  • codeforces訓練
  1. C. Hungry Games

本題大意就是找到最後g不等於0的區間個數。

主要思路:找字首和第一次大於k的下標idx(二分),然後我們發現idx+1的方案數相當於把idx+1當作左端點來算,然後我們就想到dp[i]代表以i為左端點的方案數。

總結:在比賽的時候找到了idx,但是因為沒有想到用dp來表示,所以是一直遞迴下去,然後因為一些邊界問題還是沒有寫出來。

ps:本題因為需要用到後面的狀態,所以我們從後往前二分來做。

一般這種區間的問題,就是以左端點或者右端點為起點,然後在較小的時間複雜度內找到符合條件的區間數。

#include<bits/stdc++.h>
using namespace std;
const int mod=998244353;
int dx[4]={0,0,-1,1};
int dy[4]={-1,1,0,0};
#define int long long
typedef pair<int,int> pii;
 
const int N=200010;
 
int s[N];
int a[N];
int n,k;
int dp[N];
 
void solve()
{
	cin>>n>>k;
	memset(s,0,sizeof s);
	memset(dp,0,sizeof dp);
	for(int i=1;i<=n;i++)
	{
		cin>>a[i];
		s[i]=s[i-1]+a[i];
	}
	int ans=0;
	for(int i=n;i>=1;i--)
	{
		int l=i-1,r=n+1;
		while(l+1<r)
		{
			int mid=l+r>>1;
			if(s[mid]-s[i-1]>k)
			{
				r=mid;
			}else l=mid;
		}
		if(r==n+1)dp[i]+=n-i+1;
		else dp[i]=dp[r+1]+r-i;
	}
	for(int i=1;i<=n;i++)ans+=dp[i];
	cout<<ans<<endl;
}
															                                                                                                  
signed main()
{ 
    int good_luck_to_you;
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
   // good_luck_to_you=1; 
    cin>>good_luck_to_you;
    while(good_luck_to_you--)
    {
       solve();
    }
    system("pause");   
}
  1. C. Arrow Path
    DFS
// LUOGU_RID: 171384671
#include <bits/stdc++.h>
using namespace std;

#define  int long long

const int MOD = 1e9 + 7;
const int MAX_N = 3e5 + 5;
int n;
bool vis[3][200005];
char mp[3][200005];
vector<int> g[MAX_N];
bool st[MAX_N];
int qpow(int a, int b, int m) {
    int result = 1;
    a %= m;
    while (b > 0) {
        if (b & 1) {
            result = (result * a) % m;
        }
        a = (a * a) % m;
        b >>= 1;
    }
    return result;
}
int xx[]={0,0,1,-1};
int yy[]={1,-1,0,0};
bool inmp(int x,int y){
    return x>0&&x<3&&y>0&&y<n+1;
}

void dfs(int tx,int ty){
    vis[tx][ty]=true;
    if(tx==2 and ty==n){
        return;
    }
    for(int i=0;i<4;i++){
        int x=tx+xx[i];
        int y=ty+yy[i];
        if(inmp(x,y)&&!vis[x][y]){
            vis[x][y]=true;
            if(mp[x][y]=='>'&&y+1<=n&&!vis[x][y+1]){
                dfs(x,y+1);
            }
            vis[x][y]=false;
            if(mp[x][y]=='<'&&y-1>=1&&!vis[x][y-1]){
                dfs(x,y-1);
            }
            vis[x][y]=false;

        }
    }

}


int32_t main(){
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int t=1;
    cin>>t;
    while(t--){
        cin>>n;
        for(int i=1;i<=2;i++){
            for(int j=1;j<=n;j++){
                cin>>mp[i][j];
            }
        }
        memset(vis,false,sizeof(vis));
        dfs(1,1);
        if(vis[2][n])cout<<"YES"<<'\n';
        else cout<<"NO"<<'\n';
    }
    return 0;
}

3.C. Naming Company

  • nowcoder訓練

    1.挖溝
    最小生成數的板子題
    克魯斯卡爾演算法和普利姆演算法

最小生成樹模板題。
prim演算法:
#include <bits/stdc++.h>
using namespace std;
const int maxn = 2*500000+10;
const int maxnn = 100000+10;
struct sy{
    int to;
    int next;
    int w;
} edge[maxn];
int head[maxn];
struct Node{
    int number;
    int w;
    bool operator < (const Node &n) const {
        return w>n.w;
    }
};
priority_queue<Node> pq;
bool vis[maxnn];
int cnt = 0;
int n, m;

void add_edge(int x, int y, int w)
{
    edge[++cnt].next = head[x];
    edge[cnt].to = y;
    edge[cnt].w = w;
    head[x] = cnt;
}
//普利姆演算法,利用貪心的原理求最小生成樹
int prim()
{
    int ans = 0;
    pq.push({1, 0});
    while (pq.size())
    {
        Node node = pq.top();
        pq.pop();
        int number = node.number;
        int w = node.w;
        if (vis[number]) continue;
        ans += w;
        vis[number] = true;
        //遍歷這個點的其他邊,找出沒有遍歷過的加入
        for (int i=head[number];i;i = edge[i].next)
        {
            int next = edge[i].to;
//             cout<<next<<" "<<edge[i].w<<endl;
            if (vis[next]) continue;
            pq.push({next, edge[i].w});
        }
    }
    return ans;
}

int main()
{
    int x, y ,w;
    cin>>n>>m;
    for (int i=1;i<=m;i++)
    {
        cin>>x>>y>>w;
        add_edge(x, y, w);
        add_edge(y, x, w);
    }
    int ans = prim();
    cout<<ans;
    return 0;
}
克魯斯卡爾演算法:
#include <bits/stdc++.h>
using namespace std;
const int maxn = 2*500000+10;
struct Node{
    int x, y, w;
} node[maxn];

bool comp(Node n1, Node n2)
{
    return n1.w<n2.w;
}
int fa[100000+10];
int n, m;

int find(int x)
{
    return fa[x]==0?x:fa[x] = find(fa[x]);
}


int main()
{
    cin>>n>>m;
    for (int i=1;i<=m;i++)
    {
        cin>>node[i].x>>node[i].y>>node[i].w;
    }
    sort(node+1, node+1+m, comp);
    int ans = 0;
    for (int i=1;i<=m;i++)
    {
        int x = node[i].x;
        int y = node[i].y;
        int rootx = find(x);
        int rooty = find(y);
        if (rootx==rooty) continue;
        ans += node[i].w;
        fa[rootx] = rooty;
    }
    cout<<ans<<endl;
    return 0;
}

2.道路建設
還是最小生成樹的板子

#include<bits/stdc++.h>
using namespace std;
const int maxn=10005,maxm=100005;

struct E{
    int from,next,to,dis;
}edge[maxm*2];

int c,n,m,u,v,w;
int head[maxn],cnt=0,fa[maxn];

int find(int x){
    if(fa[x]==x) return x;
    else return fa[x]=find(fa[x]);
}

void unite(int x,int y){
    fa[find(x)]=find(y);
}

void addedge(int from,int to,int dis){
    edge[++cnt].next=head[from];
    edge[cnt].from=from;
    edge[cnt].to=to;
    edge[cnt].dis=dis;
    head[from]=cnt;
}

bool cmp(E a,E b){
    return a.dis<b.dis;
}

int main(){
    scanf("%d%d%d",&c,&n,&m);
    for(int i=1;i<=n;i++){
        scanf("%d%d%d",&u,&v,&w);
        addedge(u,v,w);
        addedge(v,u,w);
    }
    int tot=0,sm=0;
    for(int i=1;i<=m;i++) fa[i]=i;
    sort(edge+1,edge+1+cnt,cmp);
    for(int i=1;i<=cnt;i++){
        if(find(edge[i].to)!=find(edge[i].from)){
            sm+=edge[i].dis;
            unite(edge[i].to,edge[i].from);
            tot++;
        }
        if(tot==m-1) break;
    }
    if(sm<=c) cout<<"Yes"<<endl;
    else cout<<"No"<<endl;
    return 0;
} 

相關文章