二维平面上给定两个点\(s,t\)和若干个圆,问是否可以从\(s\)只经过圆边到达\(t\)
\(1\le N\le 3000\)
把每个圆之间的相交或相切关系转换成两个圆可达,于是就变成了一个图论问题,给定起点和终点,问是否可以从起点到终点,一次\(bfs\)就可以实现。
但其实判断连通性也可以用并查集,这样更快。
#include <bits/stdc++.h> #define ll long long using namespace std; const int N=3005; struct Point{ int x,y; }; struct Circle{ Point c; int r; }cir[N]; bool vis[N][N],st[N]; int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int n; cin>>n; Point s,t; cin>>s.x>>s.y>>t.x>>t.y; for(int i=1;i<=n;i++){ cin>>cir[i].c.x>>cir[i].c.y>>cir[i].r; } int ss=0,tt=0; auto get_dist=[&](Point a,Point b)->ll{ return 1LL*(a.x-b.x)*(a.x-b.x)+1LL*(a.y-b.y)*(a.y-b.y); }; for(int i=1;i<=n;i++){ if(get_dist(s,cir[i].c)==1LL*cir[i].r*cir[i].r){ ss=i; } if(get_dist(t,cir[i].c)==1LL*cir[i].r*cir[i].r){ tt=i; } } for(int i=1;i<=n;i++) for(int j=i+1;j<=n;j++){ ll d=get_dist(cir[i].c,cir[j].c); ll L=1LL*(cir[i].r-cir[j].r)*(cir[i].r-cir[j].r); ll R=1LL*(cir[i].r+cir[j].r)*(cir[i].r+cir[j].r); if(L<=d&&d<=R){ vis[i][j]=vis[j][i]=1; //f[find(i)]=find(j); 并查集 } } if(!ss||!tt) cout<<"No\n"; else{ /* if(find(ss)!=find(tt)) cout<<"No\n"; else cout<<"Yes\n";s */ queue<int>q; q.push(ss); while(q.size()){ int u=q.front(); q.pop(); if(st[u]) continue; st[u]=1; if(u==tt){ cout<<"Yes\n"; return 0; } for(int v=1;v<=n;v++){ if(vis[u][v]&&!st[v]){ q.push(v); } } } cout<<"No\n"; } return 0; }
给定\(n\)个数,每个数按照质因子分解的形式给出,对于\(1\le i\le n\),若把第\(i\)个数变成\(1\),可以得到一个这些数的最小公倍数。问可以得到多少个不同的最小公倍数
显然一开始的最小公倍数就是每个质因子最大幂次的乘积,现在只需要考虑把一个数变成\(1\)后是否对质因子的最大幂次产生影响即可。
感觉不好想到的就是用 STL 去重了,其实只要满足偏序关系的都是可以去重的,一开始数论部分很好考虑,考虑去重的情况考虑了很久。
#include <bits/stdc++.h> using namespace std; int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int n; cin>>n; vector<vector<pair<int,int>>>fac(n+1); map<int,vector<int>>v; for(int i=1;i<=n;i++){ int m; cin>>m; for(int j=1;j<=m;j++){ int p,x; cin>>p>>x; fac[i].emplace_back(p,x); v[p].emplace_back(x); } } for(auto &[p,q]:v) sort(q.begin(), q.end()); vector<vector<pair<int,int>>>ans; for(int i=1;i<=n;i++){ vector<pair<int,int>>t;//这里存储的是需要除的因子的幂次,不直接求 for(auto [p,x]:fac[i]){ //如果i这个因p的幂次等于最大的幂次,说明变为1就会改变, if(x==v[p].back()){ //看次大的是否还是最大幂次,如果是,则没有影响,否则就要除以最大幂次并乘上次大幂次 int d=(v[p].size()==1)?0:v[p][int(v[p].size())-2]; if(d!=x) t.emplace_back(p,d-x); } } sort(t.begin(), t.end()); ans.push_back(t); } sort(ans.begin(), ans.end()); ans.erase(unique(ans.begin(), ans.end()),ans.end()); cout<<ans.size()<<'\n'; }
给定一个\(N\)个点的树,每条树边有一个边权,现在要求每个点\(i\)的度数小于等于\(d_i\),问保留哪些边可以使得保留下的边的边权和最大。
考虑树形\(dp\):\(dp[u][0]\)表示不选择连向父亲的边,\(dp[u][1]\)表示选择连向父亲的边
一开始我们先记录\(u\)的儿子\(v\)都不选父边的贡献和\(s\)。然后我们记录每一个\(dp[v][1]+w-dp[v][0]\),这一步是让我们有反悔的余地,这一步其实很好想到。
把所有\(dp[v][1]+w-dp[v][0]\)按照从大到小排序,然后开始转移:
令\(dp[u][0]=dp[u][1]=s\),其实\(\sum_{v\in Son_u}dp[v][0]\),然后我们开始反悔操作,加入一个\(dp[v][1]+w-dp[v][0]\)相当于撤销之前不选的操作从而选上这条边
对于\(dp[u][0]\),把前\(d[u]\)大的累计进入答案,如果遇到小于\(0\)的,当做\(0\)加入答案。
对于\(dp[u][1]\),把前\(d[u]-1\)大的累计进入答案,如果遇到小于\(0\)的,当做\(0\)加入答案。
#include <bits/stdc++.h> #define ll long long using namespace std; int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int n; cin>>n; vector<int>d(n+1); for(int i=1;i<=n;i++) cin>>d[i]; vector<vector<pair<int,int>>>e(n+1); for(int i=1;i<n;i++){ int u,v,w; cin>>u>>v>>w; e[u].emplace_back(v,w); e[v].emplace_back(u,w); } vector<vector<ll>>dp(n+1,vector<ll>(2)); //dp[u][0]:不选连向父亲的边 //dp[u][1]:选连向父亲的边 const int inf=1e9; auto dfs=[&](auto self,int u,int fa)->void{ ll s=0; vector<ll>ww; for(auto [v,w]:e[u]){ if(v==fa) continue; self(self,v,u); s+=dp[v][0]; //类似于带悔贪心的思想,假设u的连向儿子的边都没有选,通过这种差值替换每一条边的选择方案。 //这里取好和0取max,是因为如果是负数,可能不会选,而题目要求的是选的边数小于等于d[u] ww.push_back(max(dp[v][1]+w-dp[v][0],0LL)); } sort(ww.begin(), ww.end());; reverse(ww.begin(), ww.end()); dp[u][0]=s; //在所有连向儿子的边中选择最大的d[u]条替换,由于选择的是d[u]条,所以向dp[u][0]转移 for(int i=0;i<min(int(ww.size()),d[u]);i++) dp[u][0]+=ww[i]; if(d[u]==0) dp[u][1]=-inf; else{ dp[u][1]=s; //选择d[u]-1条,向dp[u][1]转移,代表要选连向u父亲的边 for(int i=0;i<min(int(ww.size()),d[u]-1);i++) dp[u][1]+=ww[i]; } }; dfs(dfs,1,1); cout<<dp[1][0]<<'\n'; }
给定一个\(n\times m\)的网格,每个格子上有一个数字\(a_{i,j}\),现在可以选择若干列和若干行,贡献就是这些列和行中的元素的并集的和。但如果某一行和某一列的交点的数字是负数,那么贡献就要减去\(10^{100}\),问可以得到的最大贡献是多少。
网格模型,把行和列分别当做点
显然,如果某一行和某一列的和小于\(0\)一定不会选
假设目前选择了一行\(i\)和一列\(j\),由于我们已经分别统计过他们的和的贡献,因此还需要减去他们交点的数字,不然会重复计数
于是可以构建这样一个网络流模型:
#include <bits/stdc++.h> #define ll long long using namespace std; const int N=205,M=N+N*N; const ll INF=1LL<<60; int n,m,S,T,cnt; int head[N],d[N],cur[N]; struct edges { int v,nxt; ll f; }e[M*2]; void add(int u,int v,ll f) { e[cnt].v=v,e[cnt].nxt=head[u],e[cnt].f=f,head[u]=cnt++; e[cnt].v=u,e[cnt].nxt=head[v],e[cnt].f=0,head[v]=cnt++; } bool bfs() { memset(d,-1,sizeof d); queue<int>q; q.push(S); d[S]=0,cur[S]=head[S]; while(q.size()) { int u=q.front(); q.pop(); for(int i=head[u];~i;i=e[i].nxt) { int v=e[i].v; ll f=e[i].f; if(d[v]==-1&&f) { d[v]=d[u]+1; cur[v]=head[v]; if(v==T) return true; q.push(v); } } } return false; } ll find(int u,ll limit) { if(u==T) return limit; ll flow=0; for(int i=cur[u];~i&&flow<limit;i=e[i].nxt) { cur[u]=i; int v=e[i].v; ll f=e[i].f; if(d[v]==d[u]+1&&f) { ll t=find(v,min(f,limit-flow)); if(!t) d[v]=-1; e[i].f-=t,e[i^1].f+=t,flow+=t; } } return flow; } ll dinic() { ll ans=0,flow; while(bfs()) while(flow=find(S,INF)) ans+=flow; return ans; } int main() { ios::sync_with_stdio(false); cin.tie(nullptr); memset(head,-1,sizeof head); cin>>n>>m; S=0,T=n+m+1; vector<vector<ll>>a(n+1,vector<ll>(m+1)); vector<ll>x(n+1),y(m+1); ll sum=0; for(int i=1;i<=n;i++) for(int j=1;j<=m;j++){ cin>>a[i][j]; x[i]+=a[i][j]; y[j]+=a[i][j]; if(a[i][j]<0) add(i,n+j,INF); else add(i,n+j,a[i][j]); } for(int i=1;i<=n;i++) if(x[i]>0)add(S,i,x[i]),sum+=x[i]; for(int i=1;i<=m;i++) if(y[i]>0)add(n+i,T,y[i]),sum+=y[i]; ll flow=dinic(); cout<<sum-flow<<'\n'; return 0; }
给定一个\(n\times n\)的网格,每个网格上有一个数字\(1\le a_{i,j}\le n^2\)。每次在网格上可以向下或向右走,问起点和终点数字相同的路径数量
\(1\le N\le 400\)
暴力做法 1:
枚举每个数字,枚举它的所有出现过的网格,然后把这些网格两两组合,每次计算用组合数直接计算\(O(1)\)的复杂度。但如果全部格子都是一样的数字,那么\(n^2\)个格子两两组合就是\(n^4\),时间复杂度显然不行。
暴力做法 2:
用\(dp\)的做法,每次枚举一种颜色,然后用\(dp\)做法\(O(n^2)\)求出,但如果有\(n^2\)种颜色,那么时间复杂度就是\(O(n^4)\)的,所以也不行。
考虑在这两种做法中做一个平衡,类似于根号分治的思想\(O(\sqrt{n^2}n^2)=O(n^3)\)
枚举每一个数字,如果这个数字的个数小于等于\(n\),那么就用暴力做法 1,否则用暴力做法 2.
#include <bits/stdc++.h> #define ll long long using namespace std; const int mod=998244353; int power(int x,int y){ int res=1; while(y){ if(y&1) res=1LL*res*x%mod; x=1LL*x*x%mod; y>>=1; } return res; } int main(){ ios::sync_with_stdio(false); cin.tie(nullptr); int n; cin>>n; vector<int>fac(2*n+1),infac(2*n+1); fac[0]=1; for(int i=1;i<=2*n;i++) fac[i]=1LL*fac[i-1]*i%mod; infac[2*n]=power(fac[2*n],mod-2); for(int i=2*n;i>=1;i--) infac[i-1]=1LL*infac[i]*i%mod; vector<vector<int>>a(n+1,vector<int>(n+1)); vector<vector<pair<int,int>>>c(n*n+1); for(int i=1;i<=n;i++) for(int j=1;j<=n;j++){ cin>>a[i][j]; c[a[i][j]].emplace_back(i,j); } auto binom=[&](int n,int m)->int{ if(n<m||m<0) return 0; return 1LL*fac[n]*infac[m]%mod*infac[n-m]%mod; }; ll ans=0; for(int k=1;k<=n*n;k++){ if(c[k].size()<=n){ for(int i=0;i<c[k].size();i++) for(int j=i;j<c[k].size();j++){ auto p=c[k][i],q=c[k][j]; if(p.second>q.second) continue; int x=q.first-p.first+q.second-p.second; int y=q.first-p.first; ans=(ans+binom(x,y))%mod; } }else{ vector<vector<int>>dp(n+1,vector<int>(n+1)); for(int i=1;i<=n;i++) for(int j=1;j<=n;j++){ if(a[i][j]==k) dp[i][j]=1; dp[i][j]=(dp[i][j]+dp[i-1][j])%mod; dp[i][j]=(dp[i][j]+dp[i][j-1])%mod; if(a[i][j]==k) ans=(ans+dp[i][j])%mod; } } } if(ans<0) ans=(ans+mod)%mod; cout<<ans<<'\n'; }