A 设备排列
较经典的一道dp
f[i][1/0]表示第i个位置是(1)否(0)放仪器的方案数
f[i][0]=f[i-1][1]+f[i-1][0]
f[i][1]=f[i-k][1]+f[i-k][0]
#pragma GCC optimize(1)
#pragma GCC optimize(2)
#pragma GCC optimize(3)
#include<bits/stdc++.h>
using namespace std;
#define endl "\n"
typedef long long ll;
#define int long long
const int mod=5000011;
int f[100005][2];
void slove(){
int n,c;
cin>>n>>c;
f[0][0]=1;
c++;
for(int i=1;i<=n;i++){
f[i][0]=f[i-1][0]+f[i-1][1];
int lxt=max(0ll,i-c);
f[i][1]=f[lxt][1]+f[lxt][0];
f[i][0]%=mod;
f[i][1]%=mod;
}
cout<<(f[n][0]+f[n][1])%mod<<endl;
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
int T=1;
// cin>>T;
while(T--){
slove();
}
return 0;
}
B 堆集装箱
类似 NOI2002 银河英雄传说 并查集
#pragma GCC optimize(1)
#pragma GCC optimize(2)
#pragma GCC optimize(3)
#include<bits/stdc++.h>
using namespace std;
#define endl "\n"
typedef long long ll;
int f[30005],s[30005],t[30005];
int find(int x){
if(f[x]==x)return x;
int fa=find(f[x]);
s[x]+=s[f[x]];
return f[x]=fa;
}
void slove(){
int q;
cin>>q;
for(int i=1;i<=30000;i++)f[i]=i,t[i]=1;
while(q--){
char op;
int x,y;
cin>>op>>x;
if(op=='M'){
cin>>y;
int fx=find(x),fy=find(y);
if(fx!=fy){
f[fx]=fy;
s[fx]+=t[fy];
t[fy]+=t[fx];
t[fx]=0;
}
}
else{
find(x);
cout<<s[x]<<endl;
}
}
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
int T=1;
// cin>>T;
while(T--){
slove();
}
return 0;
}
C 搜索航桥
航桥对应的是割边,但是这题可以使用lca来解决
对于上图可以看出,对于一棵树而言2点之间的航桥数量即为2点之间的边数(到lca前经过的节点数)。如果在上图4和6中间连接一条边,对于集合{3,4,5,6}这些节点之间都没有办法产生航桥,对于7-8之间航桥数量就从6变成了2。
对于题目给出的图,我们先将其建成一颗树,每个节点的贡献为1。 剩下一开始没接上的边(u,v)就相当于让 u->lca(u,v) v->lca(u,v)之间的点无法提供航桥。
由于题目给出了删边的操作,但是删边是较为困难的,我们可以反向思考,询问的数据先记录下来,然后从后往前建边,查询航桥。
#pragma GCC optimize(1)
#pragma GCC optimize(2)
#pragma GCC optimize(3)
#include<bits/stdc++.h>
using namespace std;
#define endl "\n"
typedef long long ll;
struct node{
int opt,x,y;
};
int n,m;
vector<int>to[30005];
int f[30005],s[30005],dep[30005],fa[30005];
int find(int x){
return f[x]==x?x:f[x]=find(f[x]);
}
void dfs(int x,int l){
fa[x]=l;
dep[x]=dep[l]+1;
for(auto y:to[x]){
if(y==l)continue;
dfs(y,x);
}
}
void upd(int x,int y){
if(dep[x]>dep[y])swap(x,y);
while(dep[x]<dep[y]){
s[y]=0;
y=fa[y];
}
while(x!=y){
s[x]=s[y]=0;
x=fa[x];y=fa[y];
}
}
int ask(int x,int y){
if(dep[x]>dep[y])swap(x,y);
int ans=0;
while(dep[x]<dep[y]){
ans+=s[y];
y=fa[y];
}
while(x!=y){
ans+=s[x]+s[y];
x=fa[x];y=fa[y];
}
return ans;
}
void slove(){
cin>>n>>m;
vector<pair<int,int>>e(m);
vector<node>b;
vector<int>ans;
set<pair<int,int>>mp;
for(int i=1;i<=n;i++)f[i]=i,s[i]=1;
for(auto &[x,y]:e)cin>>x>>y;
int q;
while(cin>>q&&q!=-1){
int x,y;
cin>>x>>y;
b.push_back({q,x,y});
if(!q)mp.insert({x,y});
else ans.push_back(0);
}
for(auto [x,y]:e){
if(mp.count({x,y}))continue;
int fx=find(x),fy=find(y);
if(fx!=fy){
f[fx]=fy;
to[x].push_back(y);
to[y].push_back(x);
mp.insert({x,y});
}
}
dfs(1,0);
for(auto [x,y]:e){
if(mp.count({x,y}))continue;
upd(x,y);
}
int j=ans.size();
while(b.size()){
auto [opt,x,y]=b.back();
if(!opt){
upd(x,y);
}
else ans[--j]=ask(x,y);
b.pop_back();
}
for(auto x:ans)cout<<x<<endl;
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
int T=1;
// cin>>T;
while(T--){
slove();
}
return 0;
}
D 太空供水
如果我们不知道这个最短时间的情况下,我们很难决定在哪些位置放置水源。但是如果知道的话,就可以依此为依据判断那个位置需要放置水源。于是可以考虑二分答案这个最短时间,然后在用dfs去判断哪些点需要放置水源,判断水源的安装个数是否在题目要求的M个以内。
#pragma GCC optimize(1)
#pragma GCC optimize(2)
#pragma GCC optimize(3)
#include<bits/stdc++.h>
using namespace std;
#define endl "\n"
typedef long long ll;
const int inf=1e8;
int n,m;
vector<int>to[300005];
bool nd[300005];
int f[300005],d[300005];
int res;
void dfs(int x,int fa,int k){
f[x]=-inf;
d[x]=inf;
for(auto y:to[x]){
if(y==fa)continue;
dfs(y,x,k);
f[x]=max(f[x],f[y]+1);
d[x]=min(d[x],d[y]+1);
}
if(nd[x]&&d[x]>k)f[x]=max(f[x],0);
if(f[x]+d[x]<=k)f[x]=-inf;
if(f[x]==k)res++,f[x]=-inf,d[x]=0;
}
bool check(int k){
res=0;
dfs(1,0,k);
if(f[1]>=0)res++;
return res<=m;
}
void slove(){
cin>>n>>m;
for(int i=1;i<=n;i++){
int x;cin>>x;
if(x)nd[i]=true;
}
for(int i=1;i<n;i++){
int a,b;
cin>>a>>b;
to[a].emplace_back(b);
to[b].emplace_back(a);
}
int l=0,r=n;
while(l<r){
int mid=l+r>>1;
if(check(mid))r=mid;
else l=mid+1;
}
cout<<r;
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
int T=1;
// cin>>T;
while(T--){
slove();
}
return 0;
}
E 太空通勤
可以看出这题是最短路并且N只有70,但是加上了一个最多经过k条通道的限制。
其实就是一道floyd,不过加上了一维来记录当前走了几步。
比较坑的一点是N只有70,M却有1e6会给出重边,建边的时候只需要建最小的。
#pragma GCC optimize(1)
#pragma GCC optimize(2)
#pragma GCC optimize(3)
#include<bits/stdc++.h>
using namespace std;
#define endl "\n"
typedef long long ll;
#define int long long
int n,m,k,q,inf;
int d[75][75][75];
void slove(){
memset(d,127,sizeof d);
inf=d[0][0][0];
cin>>n>>m;
for(int i=1;i<=m;i++){
int a,b,c;
cin>>a>>b>>c;
d[a][b][1]=min(d[a][b][1],c);
}
cin>>k>>q;
k=min(k,n);
for(int c=2;c<=k;c++){
for(int t=1;t<=n;t++){
for(int i=1;i<=n;i++){
if(i==t)continue;
for(int j=1;j<=n;j++){
if(j==i||j==t)continue;
for(int a=1,b=c-1;b>=1;a++,b--){
if(d[i][t][a]==inf||d[t][j][b]==inf)continue;
d[i][j][c]=min(d[i][j][c],d[i][t][a]+d[t][j][b]);
}
}
}
}
}
while(q--){
int a,b;
cin>>a>>b;
if(a==b){
cout<<"0\n";
continue;
}
int ans=inf;
for(int i=1;i<=k;i++)ans=min(d[a][b][i],ans);
cout<<(ans!=inf?ans:-1)<<endl;
}
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
int T=1;
// cin>>T;
while(T--){
slove();
}
return 0;
}
H 选左选右
区间dp
f[i][j]表示从区间i~j中能拿到的最多分数
对于区间i~j 如果先手取了左边那么后手一定取得 f[i+1][j]剩下的即为先手取得的分数,反之如果先手取了右边那么后手一定取得f[i][j-1]剩下的即为先手取得的分数
则可得出状态转移方程为f[i][i+k]= sum(i~i+k)-min(f[i][i+k-1],f[i+1][i+k])
#pragma GCC optimize(1)
#pragma GCC optimize(2)
#pragma GCC optimize(3)
#include<bits/stdc++.h>
using namespace std;
#define endl "\n"
typedef long long ll;
#define int long long
int f[5005][5005];
void slove(){
int n;
cin>>n;
vector<int>a(n+1,0);
for(int i=1;i<=n;i++)cin>>a[i],a[i]+=a[i-1];
for(int i=1;i<n;i++)f[i][i+1]=max(a[i]-a[i-1],a[i+1]-a[i]);
for(int k=2;k<n;k++){
for(int i=1;i+k<=n;i++){
f[i][i+k]=a[i+k]-a[i-1]-min(f[i][i+k-1],f[i+1][i+k]);
}
}
cout<<f[1][n]<<endl;
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
int T=1;
// cin>>T;
while(T--){
slove();
}
return 0;
}
I 玩捉迷藏
比赛的时候以为是数学题,完全没想到竟然是最小生成树
那么如果把这题抽象成一个最小生成树呢?
对于每个Cij可以得知房间(i-1,j]的人员总数奇偶,又因为每个房间最多只能有一个人。那么我们把Cij当作一条权值为Cij连接i-1和j的边。
该图为询问Cii的情况下得出的一颗生成树。你会发现如果可以得出每个房间是否有人的情况下,图中的点是联通的。为了花费最少的纪念币,仅需要生成一颗最小生成树即可。
#pragma GCC optimize(1)
#pragma GCC optimize(2)
#pragma GCC optimize(3)
#include<bits/stdc++.h>
using namespace std;
#define endl "\n"
#define int long long
typedef long long ll;
struct edge{
int x,y,c;
bool operator<(const edge&b)const {
return c<b.c;
}
};
int f[2005];
int find(int x){
return f[x]==x?x:f[x]=find(f[x]);
}
void slove(){
int n;
cin>>n;
vector<edge>e;
for(int i=1;i<=n;i++){
f[i]=i;
for(int j=i;j<=n;j++){
int x;
cin>>x;
e.push_back({i-1,j,x});
}
}
int ans=0;
sort(e.begin(),e.end());
for(auto [x,y,c]:e){
int fx=find(x),fy=find(y);
if(fx!=fy){
f[fx]=fy;
ans+=c;
}
}
cout<<ans<<endl;
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
int T=1;
// cin>>T;
while(T--){
slove();
}
return 0;
}