ABC342 平方数的核 反图dij 线段树优化概率dp 标记永久化线段树

D

平方数

求有多少对数乘起来是完全平方数。

典,最简单的做法是,把每个数字的每个因子,都只保留到1个,也就是把每个数里的完全平方数因子都去掉。此时剩余的不妨称为一个数字的核,两个数的核相同,乘起来就是完全平方数。

因为他们的完全平方数部分乘起来还是完全平方数,可以忽略,不是完全平方数的部分,相同,乘起来每个因子都是偶数次,也是完全平方数。

对核计数即可

int n,k;
map<int,int>mp;
int get(int x){
	int ans=1;
	rep(i,2,sqrt(x)){
		if(x%i==0){
			int cnt=0;
			while(x%i==0)x/=i,cnt++;
			if(cnt%2)ans*=i;
		}
	}
	if(x!=1)ans*=x;
	return ans;
}
void solve(void){
	cin>>n;
	int ans=0,cnt0=0;
	rep(i,1,n){
		int t;cin>>t;
		if(t==0)cnt0++;
		else{
			int x=get(t);
			ans+=mp[x];
			mp[x]++;
		}
	}
	cout<<ans+cnt0*(n-1)-(cnt0-1)*cnt0/2;
}

E

反图 dij

在这里插入图片描述
对每个点求能达到n的最晚出发时间。最晚出发时间不好求,考虑从n出发,时光倒流,求就能转化成从n出发的最短路,这就是单源最短路,可以dij

直接建反图。松弛操作,由于这个车是周期性来的,需要特殊的转移

int cnt,head[N],n,m;
bool vis[N];
ll d[N];
struct edge{
	int l,d,k,c,v;
};
priority_queue<pii>q;
vector<edge>g[N];
void dij(int s){
	d[s]=2e18;
	q.push({2e18,s});
    while(!q.empty())
    {
        int x=q.top().second;//取堆顶编号
        q.pop();
        if(vis[x])continue;//访问过,直接删掉
        vis[x]=1;//标记为访问过中
        for(auto i:g[x])
        {
                if(d[x]<i.d+i.c)continue;
                int k=(d[x]-i.l-i.c)/i.d;
                k=min(k,i.k-1);
                if(d[i.v]<i.l+k*i.d)
                {
                    d[i.v]=i.l+k*i.d;//松弛 
                    q.push({d[i.v],i.v});//相邻点入队 
                }
        }
    } 
}
void solve(void){
	cin>>n>>m;
	rep(i,1,m){
		int l,d,k,c,a,b;
		cin>>l>>d>>k>>c>>a>>b;
		g[b].push_back({l,d,k,c,a});
	}
	dij(n);
	rep(i,1,n-1){
		if(d[i]==0)cout<<"Unreachable\n";
		else cout<<d[i]<<'\n';
	}
}

F

概率dp 线段树优化

两个人的胜负和对方点数有关,但是每个人投出点数为iii的概率是和对方无关的,可以分开计算。

设投出点数为iii的概率为gig_igigig_igi会给[i+1,i+D][i+1,i+D][i+1,i+D]都有贡献,且贡献都为giD\frac{g_i}{D}Dgi,这就是个区间加,单点查,可以线段树优化dpdpdp。对手还需要i<Li<Li<L时一直投,所以[0,L−1][0,L-1][0,L1]的部分只在转移时有用,最后要清零。

投出点数的概率算完了,用它来求获胜的概率。当前点数iii,要么停下,要么接着投。设获胜概率为fif_ifi,答案就是停下和接着投取个max⁡\maxmax

停下且获胜,需要对手配合,也就是对手投出的点数比iii小,或者超过nnn,这两种情况的概率都是对gig_igi的区间求和,考虑gig_igi的前缀和sis_isi,答案就是si−1+1−sns_{i-1}+1-s_nsi1+1sn

接着投,相当于转移到了子问题,概率为子问题获胜概率的和1D∑j=i+1i+Dfj\frac {1}{D}\sum_{j=i+1}^{i+D}f_jD1j=i+1i+Dfj

所以这个dpdpdp需要倒推,并且这个转移也是个区间和,但是只有查询,不用线段树,手动维护后面DDD个位置的和即可。

int n,l,d;
db g[N],dp[N];
struct Fenwick {
    double c[N];
	
	int lowbit(int x){
		return x&(-x);
	}
	
    void upd(int l, int r, double x) {
        for (int i = l + 1; i < N; i += lowbit(i))
            c[i] += x;
        for (int i = r + 2; i < N; i += lowbit(i))
            c[i] -= x;
    }

    double ask(int x) {
        double ans = 0;
        for (int i = x + 1; i > 0; i -= lowbit(i))
            ans += c[i];
        return ans;
    }
} fenwick;
db cal(int x){
	if(x>n)return 0.0;
	db ans=1-g[n];
	if(x>l)ans+=g[x-1];
	return ans;
}
void solve(void){
	cin>>n>>l>>d;
	fenwick.upd(0,0,1);
	rep(i,0,N-5){
		g[i]=fenwick.ask(i);
		if(i<l){
			fenwick.upd(i+1,i+d,g[i]/d);
			g[i]=0;
		}
	}
	rep(i,1,N-5)g[i]+=g[i-1];
	db sum=0;
	rep1(i,N-5,0){
		if(i>n)dp[i]=0;
		else dp[i]=max(sum/d,cal(i));
		sum+=dp[i];
        if(i+d<=N-5)sum-=dp[i+d];
	}
	printf("%.15lf",dp[0]);
}

G

标记永久化线段树

在这里插入图片描述

有撤销,所以不能把操作下放。下放的话,再想撤销就要下到所有下方的子区间去撤销,破坏了线段树拆分区间只有O(log⁡n)O(\log n)O(logn)的性质。

那不下放能处理修改和查询吗?能的,使用标记永久化的思想,每次修改,都在经过的所有节点里加上这次操作,查询时累加所有经过的节点的操作即可。

对于这道题,就是可以在每个节点维护一个可动态排序的数据结构,每次修改取maxmaxmax,把maxmaxmax值插入节点的数据结构。查询时,把经过节点都查询最大值,取maxmaxmax,就考虑了所有能影响查询位置的操作的最大值。

标记永久化由于不用下放,上传,常被用于维护主席树的修改操作。

int n;
int x[N],l[N],r[N];
struct Tree{
    struct Node{
        int l,r;
        multiset<pair<ll,ll>>s;
    }tr[N<<2];
     
     
    void build(int u,int l,int r){
        tr[u].l=l,tr[u].r=r;
        tr[u].s.clear();
        if(l==r){
            return;
        }
        int mid=(l+r)>>1;
        build(ls,l,mid);    build(rs,mid+1,r);
    }
     
    void modify(int u,int l,int r,int val,int id){
    	if(r<tr[u].l||tr[u].r<l)return;
        if(tr[u].l>=l&&tr[u].r<=r){   
            tr[u].s.insert({val,id});
            return ;
        }
        else{
            modify(ls,l,r,val,id);
            modify(rs,l,r,val,id);
        }
    }
    
    void del(int u,int l,int r,int val,int id){
    	if(r<tr[u].l||l>tr[u].r)return;
	        if(tr[u].l>=l&&tr[u].r<=r){   
	            tr[u].s.erase({val,id});
	            return ;
	        }
	        else{
	            del(ls,l,r,val,id);
	            del(rs,l,r,val,id);
	        }
	    }
    
     
    ll query(int u,int x){
    	ll mx=0;
    	if(!tr[u].s.empty()){
			mx=max(mx,(*tr[u].s.rbegin()).first);
		}
        if(tr[u].l==tr[u].r)    return  mx;
        int mid=(tr[u].l+tr[u].r)>>1;
        if(x<=mid)return max(mx,query(ls,x));
        else return max(mx,query(rs,x));
    }
}t;
void solve(void){
	cin>>n;
	t.build(1,1,n);
	rep(i,1,n){
		int a;
		cin>>a;
		t.modify(1,i,i,a,0);
	}
	int q;
	cin>>q;
	rep(i,1,q){
		int op;cin>>op;
		if(op==1){
			cin>>l[i]>>r[i]>>x[i];
			t.modify(1,l[i],r[i],x[i],i);
		}
		else if(op==2){
			int p;
			cin>>p;
			t.del(1,l[p],r[p],x[p],p);
		}
		else {
			int p;
			cin>>p;
			cout<<t.query(1,p)<<'\n';
		}
	}
}
下面是使用堆优化Dijkstra 算法来求解最短路径的示例代码,其中使用了 vector 来表示的邻接表: ```cpp #include <iostream> #include <vector> #include <queue> #include <limits> using namespace std; typedef pair<int, int> pii; const int INF = numeric_limits<int>::max(); vector<int> dijkstra(const vector<vector<pii>>& graph, int source) { int n = graph.size(); vector<int> dist(n, INF); dist[source] = 0; priority_queue<pii, vector<pii>, greater<pii>> pq; pq.push({0, source}); while (!pq.empty()) { int u = pq.top().second; int d = pq.top().first; pq.pop(); if (d > dist[u]) { continue; // 已经找到了更短的路径 } for (const auto& edge : graph[u]) { int v = edge.first; int w = edge.second; if (dist[u] + w < dist[v]) { dist[v] = dist[u] + w; pq.push({dist[v], v}); } } } return dist; } int main() { int n = 5; // 的顶点数 int m = 7; // 的边数 vector<vector<pii>> graph(n); // 构建的邻接表 graph[0].push_back({1, 2}); graph[0].push_back({2, 4}); graph[1].push_back({2, 1}); graph[1].push_back({3, 7}); graph[2].push_back({3, 3}); graph[2].push_back({4, 5}); graph[3].push_back({4, 2}); graph[4].push_back({3, 1}); int source = 0; vector<int> dist = dijkstra(graph, source); cout << "Shortest distances from node " << source << ":" << endl; for (int i = 0; i < n; ++i) { cout << "Node " << i << ": " << dist[i] << endl; } return 0; } ``` 上述代码中,我们使用堆优化Dijkstra 算法来找到从源节点到其他节点的最短距离。的邻接关系使用 vector<vector<pii>> 来表示,其中 pii 表示边的目标节点和权重。你可以根据需要修改的顶点数、边数和邻接表来适应不同的场景。输出结果将会显示源节点到其他节点的最短距离。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值