CF1152D Neko and Aki’s Prank
假设根节点为1 ,那么所有的叶子结点深度必为偶数,我们对每个深度为偶数的点向上与它的父节点连边,选择该边,若冲突则不选,这样选择出来的边集就是最大的
接下来进行转换,对于深度为偶数的点,保证不冲突选边时会覆盖所有深度为奇数的点,所以选边的数量=深度为奇数点的数量
然后递推 d p i , j dp_{i,j} dpi,j表示 前 i + j i+j i+j层,选了 i i i个(,选了 j j j个 ),转移时注意 i < = j i<=j i<=j,那么对于 i + j i+j i+j为奇数时对答案产生贡献
#include
#define ll long long
using namespace std;
const ll mod=1e9+7;
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int n;
cin>>n;
vector<vector<ll>> dp(n+1,vector<ll>(n+1));
ll ans=0;
for (int i=1;i<=n;i++){
dp[i][0]=1;
if (i&1) ans++;
}
for (int i=1;i<=n;i++){
for (int j=1;j<=i;j++){
dp[i][j]=(dp[i-1][j]+dp[i][j-1])%mod;
if ((i+j)&1) ans=(ans+dp[i][j])%mod;
}
}
cout<<ans<<"\n";
return 0;
}
P3243 [HNOI2015]菜肴制作
显然要跑一个拓扑,但是题目要求并不是让字典序最小,而是尽量让序号小的数放在前面。
例二:先选1,后面可以选择5->2或4->3,如果让字典序最小,那么应该先选4->3,但是题目要求应该先让2尽快拓扑出来
反向建边跑字典序最小的拓扑,可以让小的尽可能放到后面
#include
#define ll long long
using namespace std;
void solve(){
int n,m;
cin>>n>>m;
vector<vector<int>> e(n+1);
vector<int> ru(n+1);
for (int i=1;i<=m;i++){
int a,b;
cin>>a>>b;
e[b].push_back(a);
ru[a]++;
}
priority_queue<int,vector<int>,less<int>> q;
for (int i=n;i>=1;i--){
if (!ru[i]){
q.push(i);
}
}
vector<int> ans;
while (!q.empty()){
int u=q.top();
q.pop();
ans.push_back(u);
for (auto v:e[u]){
ru[v]--;
if (!ru[v]){
q.push(v);
}
}
}
if (ans.size()!=n){
cout<<"Impossible!\n";
return;
}
reverse(ans.begin(),ans.end());
for (auto i:ans){
cout<<i<<" ";
}
cout<<"\n";
}
int main(){
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
int T;
cin>>T;
while (T--){
solve();
}
return 0;
}
P1038 [NOIP2003 提高组] 神经网络
跑拓扑然后按照题目公式算即可
注意当 c u > 0 c_u>0 cu>0时,才会将兴奋值传递给%c_v%
#include
#define ll long long
using namespace std;
const int N=105;
const int M=10005;
struct edge{
int v,w,next;
}e[M];
int head[N],cnt;
void add(int u,int v,int w){
++cnt;
e[cnt].v=v;
e[cnt].w=w;
e[cnt].next=head[u];
head[u]=cnt;
}
int main(){
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
int n,m;
cin>>n>>m;
vector<int> c(n+1),u(n+1);
for (int i=1;i<=n;i++){
cin>>c[i]>>u[i];
}
vector<int> ru(n+1),sym(n+1);
for (int i=1;i<=m;i++){
int x,y,z;
cin>>x>>y>>z;
sym[x]=1;
ru[y]++;
add(x,y,z);
}
queue<int> q;
for (int i=1;i<=n;i++){
if (!ru[i]){
q.push(i);
}
}
vector<int> ans;
while (!q.empty()){
int uu=q.front();
q.pop();
for (int i=head[uu];i;i=e[i].next){
int v=e[i].v;
ru[v]--;
if (c[uu]>0){
c[v]+=e[i].w*c[uu];
}
if (!ru[v]){
c[v]-=u[v];
if (c[v]>0&&!sym[v]){
ans.push_back(v);
}
q.push(v);
}
}
}
if (n==1&&m==0){
cout<<"1 2";
return 0;
}
if (ans.size()==0){
cout<<"NULL";
}else{
sort(ans.begin(),ans.end());
for (auto i:ans){
cout<<i<<" "<<c[i]<<"\n";
}
}
return 0;
}
P5030 长脖子鹿放置
P3355 骑士共存问题
类似的二分图最大匹配问题
核心就是如何转换为二分图,对于二分图来说,首先考虑如何分出两个阵营,某个阵营内部是不会连边的,那么我们只需要找到题目中不可能连边的两个点即可
对于长脖子鹿问题,按行的奇偶分即可
对于骑士共存问题,按(行+列)的奇偶分即可
建图后跑最大匹配即可
P2573 [SCOI2012]滑雪
首先考虑第一个问题,最多能滑到多少个点,我们只要先跑一个dfs,找到所有能够到达的点,即最多能滑到的点
对于最短路程,由于我们能无限次使用时空胶囊,那么也就是相当于为我们的搜索提供了回溯操作,那么同一条边不会走两次,于是我们最短的路程就是删去一些边后,仍能到达删边前所有点 当前图留下的边之和,这与最小生成树很像,我们可以通过类似prim对点分析的操作,来贪心,但是由于是有向边,高度越高的点应该最先与1点连通,为此prim中的堆只需先按边终点的高度排序,再按边的长度排序即可。
用kruskal的话只需要预处理排序,正常跑最小生成树即可
#include
#define ll long long
using namespace std;
const int M=2e6+5;
const int N=1e5+5;
struct edge{
int u,v,w;
}e[M];
int cnt,f[N];
bool vis[N];
vector<vector<int>> ee(N);
void dfs(int u){
for (auto v:ee[u]){
if (vis[v]) continue;
vis[v]=1;
dfs(v);
}
}
int find(int x){
if (f[x]!=x){
f[x]=find(f[x]);
}
return f[x];
}
bool merge(int x,int y){
if (find(x)!=find(y)){
f[find(x)]=find(y);
return 1;
}
return 0;
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int n,m;
cin>>n>>m;
vector<int> h(n+1);
for (int i=1;i<=n;i++){
cin>>h[i];
}
for (int i=1;i<=m;i++){
int a,b,c;
cin>>a>>b>>c;
if (h[a]>=h[b]){
e[++cnt]=edge{a,b,c};
ee[a].push_back(b);
if (h[a]==h[b]){
e[++cnt]=edge{b,a,c};
ee[b].push_back(a);
}
}else{
e[++cnt]=edge{b,a,c};
ee[b].push_back(a);
}
}
sort(e+1,e+cnt+1,[&](edge x,edge y){
if (h[x.v]==h[y.v]){
return x.w<y.w;
}
return h[x.v]>h[y.v];
});
vis[1]=1;
dfs(1);
for (int i=1;i<=n;i++){
f[i]=i;
}
int tol=0;
ll ans=0;
for (int i=1;i<=cnt;i++){
if ((!vis[e[i].v])||(!vis[e[i].u])) continue;
if (merge(e[i].v,e[i].u)){
tol++;
ans+=e[i].w;
}
}
cout<<tol+1<<" "<<ans;
return 0;
}
P2505 [HAOI2012]道路
首先考虑最短路,必须有起点或终点,考虑最暴力的方法是,找出所有的最短路并记录路径然后依次计算贡献,显然时间复杂度不允许且实现比较复杂
如果我们按照题目,考虑图上某一条边,我们只需要找到该边有多少条最短路即可,假设我们现在固定图上起点 s s s,找到从 s s s出发的最短路,然后跑一遍dijkstra,实际上有很多条边的在从 s s s出发去其它点的最短路上不会经过,假设我们删去这些边,那么最终构成的图一定是个无环图。
证明:假设有环,这个环在 s s s到某个点的最短路上一定经过,那么这段环会对该条路产生贡献,一定有不走这个环的更短路线
然后我们再考虑固定起点 s s s,固定一条边后如何计算该边的答案,设该边的起点为 u u u,终点为 v v v,从 s s s到点 u u u的路径条数记为 c n t 1 u cnt1_u cnt1u,从某个终点 t t t到点 v v v的路径条数为 c n t 2 v cnt2_v cnt2v, a n s i = c n t 1 u ∗ c n t 2 v ans_i=cnt1_u*cnt2_v ansi=cnt1u∗cnt2v ,对于 c n t 1 cnt1 cnt1我们只需跑一遍拓扑dp求,对于 c n t 2 cnt2 cnt2我们通过跑反向的拓扑求。
要注意的是对于 c n t 1 cnt1 cnt1的求解,初始化只有起点 s s s的 c n t 1 s = 1 cnt1_s=1 cnt1s=1(起点已经固定,只需找有多少条分叉的路径即可);而 c n t 2 cnt2 cnt2的求解,初始化需要每个点都赋为1(终点任意点都可以)
#include
#define ll long long
using namespace std;
const ll mod=1e9+7;
const int M=5005;
const int N=1505;
struct dij{
int num,len;
bool operator < (const dij &x)const{
return len>x.len;
}
};
struct edge{
int u,v,w,next;
}e[M],ee[M];
int head[N],headd[N],cntt,cnt,n,m;
ll ans[M];
void ins(int u,int v,int w){
e[++cnt]=edge{u,v,w,head[u]};
head[u]=cnt;
}
void inss(int u,int v,int w){
ee[++cntt]=edge{u,v,w,headd[u]};
headd[u]=cntt;
}
void solve(int s){
vector<bool> vis(n+1);
vector<int> dis(n+1,1e9);
priority_queue<dij> q;
dis[s]=0;
q.push({s,0});
while (!q.empty()){
dij x=q.top();
q.pop();
int u=x.num;
if (vis[u]) continue;
vis[u]=1;
for (int i=head[u];i;i=e[i].next){
int v=e[i].v,w=e[i].w;
if (dis[v]>dis[u]+w){
dis[v]=dis[u]+w;
q.push({v,dis[v]});
}
}
}
// for (int i=1;i<=n;i++){
// cout<
// }
// cout<<"\n";
vector<bool> good(m+1),goodd(m+1);
vector<int> ru(n+1),ruu(n+1);
for (int i=1;i<=m;i++){
if (dis[e[i].u]+e[i].w==dis[e[i].v]){
good[i]=1;
ru[e[i].v]++;
}
if (dis[ee[i].v]+ee[i].w==dis[ee[i].u]){
goodd[i]=1;
ruu[ee[i].v]++;
}
}
queue<int> q1,q2;
vector<ll> cnt1(n+1),cnt2(n+1,1);
for (int i=1;i<=n;i++){
if (!ru[i]) q1.push(i),cnt1[i]=1;
if (!ruu[i]) q2.push(i),cnt2[i]=1;
}
while (!q1.empty()){
int u=q1.front();
q1.pop();
for (int i=head[u];i;i=e[i].next){
if (!good[i]) continue;
int v=e[i].v;
cnt1[v]=(cnt1[v]+cnt1[u])%mod;
ru[v]--;
if (!ru[v]) q1.push(v);
}
}
while (!q2.empty()){
int u=q2.front();
q2.pop();
// cout<
for (int i=headd[u];i;i=ee[i].next){
int v=ee[i].v;
if (!goodd[i]) continue;
ruu[v]--;
cnt2[v]=(cnt2[v]+cnt2[u])%mod;
if (!ruu[v]) q2.push(v);
}
}
// for (int i=1;i<=n;i++){
// cout<
// }
// for (int i=1;i<=n;i++){
// cout<
// }
// cout<<"\n";
for (int i=1;i<=m;i++){
if (!good[i]) continue;
ans[i]=(ans[i]+(cnt1[e[i].u])*(cnt2[e[i].v]))%mod;
}
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin>>n>>m;
for (int i=1;i<=m;i++){
int a,b,c;
cin>>a>>b>>c;
ins(a,b,c);
inss(b,a,c);
}
for (int i=1;i<=n;i++){
solve(i);
}
for (int i=1;i<=m;i++){
cout<<(ans[i])%mod<<"\n";
}
return 0;
}