2024.11.18
今日总结
今天上午打了南外div2的比赛只做出来了第一题,后面两道是构造,想了几个都假了,最后一题没有读懂题目含义
下午思考了一会发现第二道题不可做,就去做区间Dp和状压Dp和树形Dp
1建设工程
这道题的主要思路是修改边权前后分别走一次最短路,在后面一次的最短路中对1start和ENdn之间的最短距离进行排序即可
点击查看代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<ll,ll> PII;
const ll N = 2e5 + 10;
ll n,s,t,l,k,m,ans;
ll dis1[N],dis2[N],st[N];
vector<PII> g[N];
void Add(ll a,ll b,ll c)
{
g[a].push_back({b,c});
}
void dijkstra_1(ll s)
{
priority_queue<PII,vector<PII>,greater<PII>> pq;
for(ll i = 1;i <= n;i ++)
st[i] = 0;
pq.push({0,s});
dis1[s] = 0;
while(!pq.empty())
{
auto t = pq.top();
pq.pop();
ll u = t.second;
if(st[u]) continue;
st[u] = 1;
for(PII i : g[u])
{
if(dis1[i.first] > dis1[u] + i.second)
{
dis1[i.first] = dis1[u] + i.second;
pq.push({dis1[i.first],i.first});
}
}
}
}
void dijkstra_2(ll s)
{
priority_queue<PII,vector<PII>,greater<PII>> pq;
for(ll i = 1;i <= n;i ++)
st[i] = 0;
pq.push({0,s});
dis2[s] = 0;
while(!pq.empty())
{
auto t = pq.top();
pq.pop();
ll u = t.second;
if(st[u]) continue;
st[u] = 1;
for(PII i : g[u])
{
if(dis2[i.first] > dis2[u] + i.second)
{
dis2[i.first] = dis2[u] + i.second;
pq.push({dis2[i.first],i.first});
}
}
}
}
int main()
{
freopen("build.in","r",stdin);
freopen("build.out","w",stdout);
memset(dis1,0x7f,sizeof(dis1));
memset(dis2,0x7f,sizeof(dis2));
scanf("%lld%lld%lld%lld%lld%lld",&n,&m,&s,&t,&l,&k);
for(ll i = 1;i <= m;i ++)
{
ll a,b,c;
scanf("%lld%lld%lld",&a,&b,&c);
Add(a,b,c),Add(b,a,c);
}
dijkstra_1(s),dijkstra_2(t);
if(dis1[t] <= k)
{
printf("%lld\n",n * (n - 1) / 2);
return 0;
}
sort(dis1 + 1,dis1 + n + 1),sort(dis2 + 1,dis2 + n + 1);
for(ll i = n,j = 0;i > 0;i --)
{
while(dis1[i] + l + dis2[j + 1] <= k && j < n) j ++;
ans += j;
}
printf("%lld\n",ans);
return 0;
}
2:炮兵阵地
这道题是一道状压Dp的题目,主要思考思路就是在最基础的状压Dp的基础上对每一行做两次判断是否合法,列上判断不变,由题目可知他会攻击相邻的两个格子而不是一个格子
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N = 110,M = 1 << 10;
int n,m;
int g[N],cnt[M],dp[4][M][M];
vector<int> state;
vector<int>heap[M]; // 存放合法状态
bool check(int st)
{
return !(st & st >> 1 || st & st >> 2);
}
int count(int state) // 统计一层的状态
{
int res = 0;
while(state)
{
res += state & 1;
state >>= 1;
}
return res;
}
int main()
{
scanf("%d%d",&n,&m);
for(int i = 1,j = 0;i <= n;i ++,j = 0)
{
for(char c;j < m && cin >> c;j ++)
{
g[i] += (c == 'H') << j;
}
}
for(int i = 0;i < 1 << m;i ++) // 找出每一列的状态
{
if(check(i))
{
state.push_back(i);
cnt[i] = count(i);
}
}
for(int i : state)
{
for(int j : state)
{
if(!(i & j)) heap[i].push_back(j);
}
}
for(int i = 1;i <= n + 2;i ++)
{
for(int st : state)
{
if(!(g[i] & st))
{
for(int a : heap[st])
{
for(int b : heap[a])
{
if(!(st & b)) dp[i & 1][st][a] = max(dp[i & 1][st][a],dp[i - 1 & 1][a][b] + cnt[st]);
}
}
}
}
}
printf("%d\n",dp[n + 2 & 1][0][0]);
return 0;
}
3:能量项链
这道题主要是一个区间Dp的应用,一定要注意他有可能成环,并且根据题意len的区间一定要到n + 1,对于成环的解决,在最后对每一个可能会成环的区间取一个最大值即可
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N = 110,M = N * 2;
int n;
int w[N],dp[M][M];
signed main()
{
scanf("%lld",&n);
for(int i = 1;i <= n;i ++)
{
scanf("%lld",&w[i]);
w[n + i] = w[i];
}
for(int len = 2;len <= n + 1;len ++)
{
for(int l = 1;l + len - 1 <= n * 2;l ++)
{
int r = l + len - 1;
if(len == 2) dp[l][r] = 0;
else
{
for(int k = l + 1;k < r;k ++)
{
int E = w[l] * w[k] * w[r];
dp[l][r] = max(dp[l][r],dp[l][k] + dp[k][r] + E);
}
}
}
}
int res = 0;
for(int i = 1;i <= n;i ++)
res = max(res,dp[i][i + n]);
printf("%lld\n",res);
return 0;
}
4:石子合并
这道题是石子合并的最终加强版,这道题主要是还应用到了GarsiaWachs算法,其他的地方很板子
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
int n,w,ans;
vector<int> l;
int Solve()
{
int k = l.size() - 2,q = -1;
for(int i = 0;i < l.size() - 2;i ++)
{
if(l[i] <= l[i + 2])
{
k = i;
break;
}
}
int t = l[k] + l[k + 1];
l.erase(l.begin() + k),l.erase(l.begin() + k);
for(int i = k - 1;i >= 0;i --)
{
if(l[i] > t)
{
q = i;
break;
}
}
l.insert(l.begin() + q + 1,t);
return t;
}
signed main()
{
// freopen("stone.in","r",stdin);
// freopen("stone.out","w",stdout);
scanf("%d",&n);
for(int i = 1;i <= n;i ++)
{
scanf("%d",&w);
l.push_back(w);
}
for(int i = 0;i < n - 1;i ++)
ans += Solve();
printf("%lld",ans);
return 0;
}
5:加分二叉树
这道题第一眼看题目是一道树上题目,但是只要仔细观察,这是涉及到区间求和的最大值,这显然是一道Dp题,在观察有分界点,可以分出两个区间,则可以说明这是到区间Dp的题目
这道题的主要思路是对左右两个区间的最大值和当前根节点的值加和找所有的最大值即可
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N = 35;
int n;
int w[N],g[N][N],dp[N][N];
void dfs(int l,int r)
{
if(l > r) return;
int k = g[l][r];
printf("%d ",k);
dfs(l,k - 1),dfs(k + 1,r);
}
int main()
{
// freopen("tree.in","r",stdin);
// freopen("tree.out","w",stdout);
scanf("%d",&n);
for(int i = 1;i <= n;i ++)
scanf("%d",&w[i]);
for(int len = 1;len <= n;len ++)
{
for(int l = 1;l + len - 1 <= n;l ++)
{
int r = l + len - 1;
for(int k = l;k <= r;k ++)
{
int left = k == l ? 1 : dp[l][k - 1];
int right = k == r ? 1 : dp[k + 1][r];
int score = left * right + w[k];
if(l == r) score = w[k];
if(dp[l][r] < score)
{
dp[l][r] = score;
g[l][r] = k;
}
}
}
}
printf("%d\n",dp[1][n]);
dfs(1,n);
return 0;
}
6:二叉苹果树
这道题是一道树形Dp的题目,还应用了分层背包,主要是因为给定的数中有容积,所以我们可以想到用分层背包,而这道题又是一个基于树的Dp
这道题主要是根据根节点所能连的边中苹果的数量进行动态转移
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N = 110;
const int M = N * 2;
int n,m;
int dp[M][M];
int head[N],nxt[M],to[M],w[M],idx;
void Add(int a,int b,int c)
{
idx ++;
to[idx] = b;
w[idx] = c;
nxt[idx] = head[a];
head[a] = idx;
}
void dfs(int u,int fa)
{
for(int i = head[u];i;i = nxt[i])
{
int v = to[i];
if(v == fa) continue;
dfs(v,u);
for(int j = m;j >= 0;j --)
for(int k = 0;k < j;k ++)
dp[u][j] = max(dp[u][j],dp[u][j - k - 1] + dp[v][k] + w[i]);
}
}
int main()
{
scanf("%d%d",&n,&m);
for(int i = 1;i <= n - 1;i ++)
{
int a,b,c;
scanf("%d%d%d",&a,&b,&c);
Add(a,b,c),Add(b,a,c);
}
dfs(1,-1);
printf("%d\n",dp[1][m]);
return 0;
}