暑假专题训练一
7月份的训练,比赛->补题->比赛->补题,回家之后写一写题解整理一下
一开始的摸底,专题比较杂,也比较简单,3道双指针,3道线段树/分块,3道并查集,3道最短路
A(HDU5672)
题目大意:给定一个长度为 n n n的字符串 s s s,问有多少个子串包含了至少 k k k个字母
10 ≤ n ≤ 1 e 6 , 1 ≤ k ≤ 26 10 \le n \le 1e6,1 \le k \le 26 10≤n≤1e6,1≤k≤26
思路:移动双指针即可,尺取移动的时候留意一下哪个指针每次移动1
#include<bits/stdc++.h>
using namespace std;
#define rep(i,a,b) for(int i=a;i<=(b);++i)
#define trav(a,x) for(auto&a : x)
#define all(x) x.begin(),x.end()
#define sz(x) (int) x.size()
typedef pair<int,int> pii;
typedef vector<int> vi;
typedef long long ll;
map<char,int> mp;
void work()
{
mp.clear();
string s;
cin>>s;
int k;
cin>>k;
int l=0,r=-1,n=sz(s),cnt=0;
ll ans=0;
while(l<n&&r<n)
{
while(r<n)
{
if(cnt==k) {ans+=n-r;break;}
if(r==n-1) break;
r++;
if(!mp[s[r]]) cnt++;
mp[s[r]]++;
}
if(mp[s[l]]==1) cnt--;
mp[s[l]]--;
l++;
}
cout<<ans<<'\n';
}
int main()
{
cin.tie(0);
cin.sync_with_stdio(0);
int t=1;
cin>>t;
while(t--)
work();
return 0;
}
B(HDU6103)
定义两个长为 n n n的字符串的距离为:
d i s A , B = ∑ i = 0 n − 1 ∣ A i − B n − 1 − i ∣ dis_{A,B}=\sum^{n-1}_{i=0}|A_i-B_{n-1-i}| disA,B=∑i=0n−1∣Ai−Bn−1−i∣
其中 A i , B i A_i,B_i Ai,Bi为字符的ascii码
找到给定字符串S中的距离小于或等于 m m m的最长的两个不重叠子串,输出他们的长度
其中 m ≤ 5000 , ∣ S ∣ ≤ 20000 m \le 5000,|S| \le 20000 m≤5000,∣S∣≤20000
思路:
注意到要找的两个子串长度相同,那么我们可以考虑枚举一个中心点 i i i,当我确定了 S 1 S_1 S1的起始指针
S 2 S_2 S2的起始指针只需要依着它对称过去就行了
然后,对于当前中心点 i i i,枚举每个左指针 l l l,我们都可以双指针找到最大的 S 1 S_1 S1的右指针 m l ml ml,使得 S 1 S_1 S1和 S 2 S_2 S2的距离不超过 m m m
注意中心点可能不是仅仅是某个字符所在的位置,还可能是字符之间
所以这里写了两遍双指针
#include<bits/stdc++.h>
using namespace std;
#define rep(i,a,b) for(int i=a;i<=(b);++i)
#define trav(a,x) for(auto&a : x)
#define all(x) x.begin(),x.end()
#define sz(x) (int) x.size()
typedef pair<int,int> pii;
typedef vector<int> vi;
typedef long long ll;
void work()
{
int m,ans=0;
string s;
cin>>m;
cin>>s;
int len=sz(s);
//第一遍双指针,当中心点刚好是某个字符的时候
for(int i=0;i<len;i++)
{
int sum=0;
for(int l=i,r=i,ml=i,mr=i;0<=l&&r<len;l--,r++)
{
sum+=abs(s[r]-s[l]);
while(sum>m&&ml>=0&&mr<len) sum-=abs(s[mr++]-s[ml--]);
ans=max(ans,ml-l+(ml!=i));
}
}
//第二遍双指针,当中心点在字符之间时
for(int i=0;i<len;i++)
{
int sum=0;
for(int l=i,r=i+1,ml=i,mr=i+1;0<=l&&r<len;l--,r++)
{
sum+=abs(s[r]-s[l]);
while(sum>m&&ml>=0&&mr<len) sum-=abs(s[mr++]-s[ml--]);
ans=max(ans,ml-l+1);
}
}
cout<<ans<<'\n';
}
int main()
{
cin.tie(0);
cin.sync_with_stdio(0);
int t=1;
cin>>t;
while(t--)
work();
return 0;
}
C(HDU4737)
定义 f ( i , j ) f(i,j) f(i,j)为 [ i , j ] ( i ≤ j ) [i,j](i \le j) [i,j](i≤j)之间元素的按位或之和
给定一个数 m m m和长度为 n n n的数组 a a a
计数使得 f ( i , j ) < m f(i,j)<m f(i,j)<m的数对 ( i , j ) (i,j) (i,j)的数量
其中 1 ≤ n ≤ 1 e 5 , 1 ≤ a i ≤ 2 30 1 \le n \le 1e5,1 \le a_i \le 2^{30} 1≤n≤1e5,1≤ai≤230
思路:由于或运算没有逆元,我们左移双指针的时候不方便一步”撤销“
而这里位运算每一位可以拆开考虑,所以直接双指针维护 [ i , j ] [i,j] [i,j]之间每一位 1 1 1的数量即可
#include<bits/stdc++.h>
using namespace std;
#define rep(i,a,b) for(int i=a;i<(b);++i)
#define trav(a,x) for(auto&a : x)
#define all(x) x.begin(),x.end()
#define sz(x) (int) x.size()
typedef pair<int,int> pii;
typedef vector<int> vi;
typedef long long ll;
ll work()
{
int n,m;
cin>>n>>m;
vi a(n),cnt(40);
int cdefesg;
rep(i,0,n)
cin>>a[i];
int l=0,r=0,num=a[0];
for(int i=0;i<=30;i++)
if(a[0]>>i&1) cnt[i]++;
ll ans=0;
while(r<n)
{
while(num>=m&&l<r)
{
for(int i=0;i<=30;i++)
{
if(a[l]>>i&1)
{
cnt[i]--;
if(cnt[i]==0) num-=1<<i;
}
}
l++;
}
if(num<m) ans+=r-l+1;
if(r==n-1) break;
r++;
for(int i=0;i<=30;i++)
{
if(a[r]>>i&1)
{
if(cnt[i]==0) num+=1<<i;
cnt[i]++;
}
}
}
return ans;
}
int main()
{
cin.tie(0);
cin.sync_with_stdio(0);
int t=1;
cin>>t;
for(int i=1;i<=t;i++)
cout<<"Case #"<<i<<": "<<work()<<'\n';
return 0;
}
D(HDU1166)
敌兵布阵,线段树板子
第一行一个整数T,表示有T组数据。
每组数据第一行一个正整数N(N<=50000),表示敌人有N个工兵营地,接下来有N个正整数,第i个正整数ai代表第i个工兵营地里开始时有ai个人(1<=ai<=50)。
接下来每行有一条命令,命令有4种形式:
(1) Add i j,i和j为正整数,表示第i个营地增加j个人(j不超过30)
(2)Sub i j ,i和j为正整数,表示第i个营地减少j个人(j不超过30);
(3)Query i j ,i和j为正整数,i<=j,表示询问第i到第j个营地的总人数;
(4)End 表示结束,这条命令在每组数据最后出现
#include<bits/stdc++.h>
using namespace std;
#define rep(i,a,b) for(int i=a;i<=(b);++i)
#define trav(a,x) for(auto&a : x)
#define all(x) x.begin(),x.end()
#define sz(x) (int) x.size()
#define lson t<<1,l,mid
#define rson t<<1|1,mid+1,r
typedef pair<int,int> pii;
typedef vector<int> vi;
typedef long long ll;
const int maxn=50005;
struct Tree
{
int l,r;
int sum;
}tr[maxn<<2];
int a[maxn];
void pushup(int t)
{
tr[t].sum=tr[t<<1].sum+tr[t<<1|1].sum;
}
void build(int t,int l,int r)
{
tr[t].l=l;tr[t].r=r;
if(l==r)
{
tr[t].sum=a[l];
return;
}
int mid=(tr[t].l+tr[t].r)>>1;
build(lson);build(rson);
pushup(t);
}
void Modify(int t,int key,int v)
{
if(tr[t].l==tr[t].r)
{
tr[t].sum+=v;
return;
}
int mid=(tr[t].l+tr[t].r)>>1;
if(key<=mid) Modify(t<<1,key,v);
else Modify(t<<1|1,key,v);
pushup(t);
}
int Query(int t,int ll,int rr)
{
if(ll<=tr[t].l&&tr[t].r<=rr) return tr[t].sum;
int res=0;
int mid=(tr[t].l+tr[t].r)>>1;
if(ll<=mid) res+=Query(t<<1,ll,rr);
if(rr>mid) res+=Query(t<<1|1,ll,rr);
return res;
}
void work()
{
int n;
cin>>n;
rep(i,1,n)
cin>>a[i];
build(1,1,n);
string str;
cin>>str;
while(str!="End")
{
int x,y;
if(str=="Add")
{
cin>>x>>y;
Modify(1,x,y);
}
if(str=="Sub")
{
cin>>x>>y;
Modify(1,x,-y);
}
if(str=="Query")
{
cin>>x>>y;
cout<<Query(1,x,y)<<'\n';
}
cin>>str;
}
int absd;
rep(i,1,n<<2)
tr[i]=(Tree){0,0,0};
}
int main()
{
cin.tie(0);
cin.sync_with_stdio(0);
int t=1;
cin>>t;
for(int i=1;i<=t;i++)
{
cout<<"Case "<<i<<":\n";
work();
}
return 0;
}
E(HDU4217)
给定一个长为 n n n的 1 , 2 , 3 , … … , n {1,2,3,……,n} 1,2,3,……,n的 a a a数组,给定 K K K个数,每次从数组中拿出第 K i K_i Ki小的数并删去,求拿出数的总和
思路:线段树
由于本身 a a a数组就是 1 1 1到 n n n排好了的,所以找第 K i K_i Ki小的数,每个节点只要维护自己的子树中有多少个叶子节点
查询的时候
如果 K i ≤ 左儿子节点数 K_i \le 左儿子节点数 Ki≤左儿子节点数,那么直接在左子树中找第 K i K_i Ki小
如果 K i > 左儿子节点数 K_i> 左儿子节点数 Ki>左儿子节点数,在右子树中找第 K i − 左儿子节点数 K_i-左儿子节点数 Ki−左儿子节点数小即可
我们将叶子节点的权值计为1,维护区间和其实就相当于维护子树中叶子节点的个数。删除操作只需要把单点修改成0即可
对于初学线段树而言,也算是比较妙的一道题
#include<bits/stdc++.h>
using namespace std;
#define rep(i,a,b) for(int i=a;i<=(b);++i)
#define trav(a,x) for(auto&a : x)
#define all(x) x.begin(),x.end()
#define sz(x) (int) x.size()
typedef pair<int,int> pii;
typedef vector<int> vi;
typedef long long ll;
const int maxn=262200;
struct Tree
{
int l,r,id;
int sum;
}tr[maxn<<2];
int pos=0;
void pushup(int t)
{
tr[t].sum=tr[t<<1].sum+tr[t<<1|1].sum;
}
void build(int t,int l,int r)
{
tr[t].l=l;tr[t].r=r;
if(l==r)
{
tr[t].id=++pos;
tr[t].sum=1;
return;
}
int mid=l+r>>1;
build(t<<1,l,mid);
build(t<<1|1,mid+1,r);
pushup(t);
}
int Query(int t,int key)
{
int res;
if(tr[t].l==tr[t].r)
{
tr[t].sum=0;
return tr[t].id;
}
if(key<=tr[t<<1].sum) res=Query(t<<1,key);
else res=Query((t<<1)|1,key-tr[t<<1].sum);
pushup(t);
return res;
}
ll work()
{
int n,k;
cin>>n>>k;
build(1,1,n);
pos=0;
ll ans=0;
for(int i=1;i<=k;i++)
{
int kth;
cin>>kth;
ans+=Query(1,kth);
}
rep(i,1,n<<2)
tr[i]=(Tree){0,0,0,0};
return ans;
}
int main()
{
cin.tie(0);
cin.sync_with_stdio(0);
int t=1;
cin>>t;
for(int i=1;i<=t;i++)
cout<<"Case "<<i<<": "<<work()<<'\n';
return 0;
}
F(HDU3333)
给定一个长度为 n n n的数组 a a a, q q q个询问
每次给出两个数 i , j i,j i,j,查询 [ i , j ] [i,j] [i,j]之间不相同的数字之和
其中 1 ≤ n ≤ 30000 , 0 ≤ a i ≤ 1 e 9 , 1 ≤ Q ≤ 1 e 5 1 \le n \le 30000,0 \le a_i \le 1e9,1 \le Q \le 1e5 1≤n≤30000,0≤ai≤1e9,1≤Q≤1e5
思路:
离线+线段树
如果题目没有要求查询不相同的数字之和,那么这就是一个线段树区间查询的板子题
我们考虑,如果一个区间中有一个数字出现了多次,那么我们只需要把其中一个出现的地方不变,其他地方全赋值成0就可以了
为了将这个的复杂度降为线性,我们先把询问区间记录下来,从左到右处理所有询问区间,对于每个数字,只记录它上一次出现过的位置
如果扫描到 i i i,发现 a [ i ] a[i] a[i]没出现过,就在线段树上赋值,出现过,就把上一次出现过的位置赋为0,然后在给 i i i赋上 a [ i ] a[i] a[i],就可以保证不加入重复的数字了
#include<bits/stdc++.h>
using namespace std;
#define rep(i,a,b) for(int i=a;i<=(b);++i)
#define trav(a,x) for(auto&a : x)
#define all(x) x.begin(),x.end()
#define sz(x) (int) x.size()
typedef pair<int,int> pii;
typedef vector<int> vi;
typedef long long ll;
const int maxn=30005;
const int maxq=1e5+5;
struct Tree
{
ll sum;int l;int r;
}tr[maxn<<2];
struct Interval
{
int l;int r;int id;
};
bool cmp(Interval x,Interval y)
{
return x.r<y.r;
};
void pushup(int t)
{
tr[t].sum=tr[t<<1].sum+tr[t<<1|1].sum;
}
void build(int t,int l,int r)
{
tr[t].l=l;tr[t].r=r;
if(l==r)
{
tr[t].sum=0;
return;
}
int mid=l+r>>1;
build(t<<1,l,mid);
build(t<<1|1,mid+1,r);
pushup(t);
}
void Modify(int t,int key,int val)
{
if(tr[t].l==tr[t].r)
{
tr[t].sum=val;
return;
}
int mid=(tr[t].l+tr[t].r)>>1;
if(key<=mid) Modify(t<<1,key,val);
else Modify(t<<1|1,key,val);
pushup(t);
}
ll Query(int t,int L,int R)
{
if(L<=tr[t].l&&tr[t].r<=R) return tr[t].sum;
int mid=(tr[t].l+tr[t].r)>>1;
ll res=0;
if(L<=mid) res+=Query(t<<1,L,R) ;
if(R>mid) res+=Query(t<<1|1,L,R);
return res;
}
void work()
{
int n;
cin>>n;
vi a(n+1);
rep(i,1,n)
cin>>a[i];
map<int,int> pre;
build(1,1,n);
int q;
cin>>q;
vector<Interval> que(q+1);vector<ll> ans(q+1);
rep(i,1,q)
{cin>>que[i].l>>que[i].r;que[i].id=i;}
sort(que.begin()+1,que.end(),cmp);
for(int i=1,j=1;i<=n;i++)
{
if(!pre[a[i]])
{
Modify(1,i,a[i]);
pre[a[i]]=i;
}
else
{
Modify(1,pre[a[i]],0);
Modify(1,i,a[i]);
pre[a[i]]=i;
}
while(j<=q&&que[j].r==i)
{
ans[que[j].id]=Query(1,que[j].l,que[j].r);
j++;
}
}
rep(i,1,q)
cout<<ans[i]<<'\n';
rep(i,1,n<<2)
tr[i].l=tr[i].r=tr[i].sum=0;
}
int main()
{
cin.tie(0);
cin.sync_with_stdio(0);
int t=1;
cin>>t;
while(t--)
work();
return 0;
}
G(HDU2473)
初始有 n n n个孤立的点, m m m个操作分为两种
M x,y,在x,y之间连一条无向边
S x,将与x相连的所有边全部删除
问所有操作之后,连通块的个数
1 ≤ n ≤ 1 e 5 , 1 ≤ m ≤ 1 e 6 1 \le n \le 1e5,1 \le m \le 1e6 1≤n≤1e5,1≤m≤1e6
思路:
一开始看错题了,想着反着按照时间顺序用并查集来维护。
事实上这道题直接模拟的话用dfs好像也能做。但是并查集是一个非常神奇值得学习技巧其的东西
从并查集的视角来看的话M操作就是Merge,S操作,我们不好维护,因为并查集是不能删除一个点的
这里因为 S S S操作的数量比较少,删除操作,我们可以把某个点的直接父亲连向一个新点。
这里我们再新开一个 a a a数组,作为某个 i i i的直接父亲,因为如果把 i i i本身加入并查集的话,会导致删除它的时候,把它的儿子也一起给删掉
#include<bits/stdc++.h>
using namespace std;
#define rep(i,a,b) for(int i=a;i<(b);++i)
#define trav(a,x) for(auto&a : x)
#define all(x) x.begin(),x.end()
#define sz(x) (int) x.size()
typedef pair<int,int> pii;
typedef vector<int> vi;
typedef long long ll;
const int maxn=2e6+5;
int n,m;
int a[maxn],fat[maxn];
int find(int x)
{
if(fat[x]==x) return x;
fat[x]=find(fat[x]);
return fat[x];
}
void uni(int x,int y)
{
int fx=find(x),fy=find(y);
if(fx==fy) return;
fat[fx]=fy;
}
set<int> S;
int work()
{
S.clear();
int now=n;
rep(i,0,maxn-5) fat[i]=i,a[i]=i;
rep(i,0,m)
{
char op;int x,y;
cin>>op;
if(op=='M')
{
cin>>x>>y;
uni(a[x],a[y]);
}
if(op=='S')
{
cin>>x;
a[x]=++now;
}
}
rep(i,0,n)
S.insert(find(a[i]));
return S.size();
}
int main()
{
cin.tie(0);
cin.sync_with_stdio(0);
int t=1;
cin>>n>>m;
while(n||m)
{
cout<<"Case #"<<t<<": "<<work()<<'\n';
cin>>n>>m;
t++;
}
return 0;
}
H(HDU1512)
题目大意:
有 n n n只猴子,每只猴子有一个实力值 w i w_i wi。一开始互相不认识,有M个事件,为第 x , y x,y x,y这两只猴子遇上了,如果认识则输出-1,不认识则将它们认识的两个最强的猴子 x 1 , y 1 x1,y1 x1,y1叫出来, x 1 , y 1 x1,y1 x1,y1的实力值除以2之后将第 x , y x,y x,y只猴子的关系网合并
思路:
可并堆模板……
#include<bits/stdc++.h>
using namespace std;
#define rep(i,a,b) for(int i=a;i<=(b);++i)
#define trav(a,x) for(auto&a : x)
#define all(x) x.begin(),x.end()
#define sz(x) (int) x.size()
typedef pair<int,int> pii;
typedef vector<int> vi;
typedef long long ll;
const int maxn=1e5+5;
int n,m;
int val[maxn],fat[maxn],ch[maxn][2],dis[maxn];
int find(int x)
{
if(fat[x]==x) return x;
fat[x]=find(fat[x]);
return fat[x];
}
int merge(int x,int y)
{
if(!x||!y) return x|y;
if(val[x]<val[y]) swap(x,y);
//printf("x=%d,y=%d,ch[x][1]=%d\n",x,y,ch[x][1]);
ch[x][1]=merge(ch[x][1],y);
fat[ch[x][1]]=x;
if(dis[ch[x][0]]<dis[ch[x][1]]) swap(ch[x][0],ch[x][1]);
if(ch[x][1]==0) dis[x]=0;
else dis[x]=dis[ch[x][1]]+1;
return x;
}
int pop(int x)
{
int l=ch[x][0],r=ch[x][1];
fat[l]=l;
fat[r]=r;
dis[x]=ch[x][0]=ch[x][1]=0;
return merge(l,r);
}
void work()
{
rep(i,1,n)
{
ch[i][0]=ch[i][1]=dis[i]=0;
cin>>val[i];
fat[i]=i;
}
cin>>m;
while(m--)
{
int x,y;
cin>>x>>y;
int fx=find(x),fy=find(y);
if(fx==fy) cout<<"-1\n";
else
{
val[fx]>>=1;
int u=pop(fx);
u=merge(u,fx);
val[fy]>>=1;
int v=pop(fy);
v=merge(v,fy);
//cout<<val[2]<<'\n';
//printf("u=%d,v=%d\n",u,v);
//cout<<merge(u,v)<<'\n';
cout<<val[merge(u,v)]<<'\n';
}
}
}
int main()
{
cin.tie(0);
cin.sync_with_stdio(0);
//int t=1;
while(cin>>n)
work();
return 0;
}
I(luoguP1668)
给定 n n n个区间,用最少的区间将 [ 1 , T ] [1,T] [1,T]覆盖
很经典的贪心问题,将区间按照左端点排序,我们只要找到能够覆盖上一个be且r最大的区间即可
#include<bits/stdc++.h>
using namespace std;
#define rep(i,a,b) for(int i=a;i<(b);++i)
#define trav(a,x) for(auto&a : x)
#define all(x) x.begin(),x.end()
#define sz(x) (int) x.size()
typedef pair<int,int> pii;
typedef vector<int> vi;
typedef long long ll;
struct Interval
{
int l,r;
};
bool cmp(Interval x,Interval y)
{
return x.l<y.l;
}
void work()
{
int n,ed;
cin>>n>>ed;
vector<Interval> a(n);
rep(i,0,n)
cin>>a[i].l>>a[i].r;
sort(a.begin(),a.end(),cmp);
int i=0,j=0,be=1,ans=0;
bool flag=false;
while(i<n&&j<n)
{
int r=-1;
while(j<n&&a[j].l<=be)
{
r=max(r,a[j].r);
j++;
}
ans++;
if(r==ed) {flag=true;break;}
if(r==-1) {flag=false;break;}
be=r+1;
i=j;
}
if(!flag) ans=-1;
cout<<ans<<'\n';
}
int main()
{
cin.tie(0);
cin.sync_with_stdio(0);
int t=1;
//cin>>t;
while(t--)
work();
return 0;
}
J(luoguP2939)
n n n个点, m m m条边的无向图,可以选择 k k k条边免费,求 1 1 1到 n n n的最短路
1 ≤ n ≤ 10000 , 1 ≤ m ≤ 50000 , 1 ≤ k ≤ 20 1 \le n \le 10000,1 \le m \le 50000,1 \le k \le 20 1≤n≤10000,1≤m≤50000,1≤k≤20
经典分层图最短路,回忆复健了一下,第 i i i层向 i + 1 i+1 i+1层连权值为0的单向边
#include<bits/stdc++.h>
using namespace std;
#define rep(i,a,b) for(int i=a;i<=(b);++i)
#define trav(a,x) for(auto&a : x)
#define all(x) x.begin(),x.end()
#define sz(x) (int) x.size()
typedef pair<int,int> pii;
typedef vector<int> vi;
typedef long long ll;
const int maxn=10005;
struct Edge
{
int to;int dis;
};
vector<Edge> adj[maxn*21];
ll dis[maxn*21];
struct node
{
int u;ll val;
};
priority_queue<node> q;
bool operator < (const node &a,const node &b)
{
return a.val>b.val;
}
void dijkstra()
{
memset(dis,0x3f,sizeof dis);
dis[1]=0;
q.push((node){1,0});
while(!q.empty())
{
node tmp=q.top();q.pop();
int u=tmp.u,d=tmp.val;
if(dis[u]!=d) continue;
trav(e,adj[u])
{
int v=e.to;
if(dis[u]+e.dis<dis[v])
{
dis[v]=dis[u]+e.dis;
q.push((node){v,dis[v]});
}
}
}
}
void work()
{
int n,m,k;
cin>>n>>m>>k;
rep(i,1,m)
{
int x,y,z;
cin>>x>>y>>z;
adj[x].push_back((Edge){y,z});
adj[y].push_back((Edge){x,z});
rep(i,1,k)
{
adj[x+i*n].push_back((Edge){y+i*n,z});
adj[y+i*n].push_back((Edge){x+i*n,z});
adj[x+(i-1)*n].push_back((Edge){y+i*n,0});
adj[y+(i-1)*n].push_back((Edge){x+i*n,0});
}
}
rep(i,1,k)
adj[n+(i-1)*n].push_back((Edge){n+i*n,0});
dijkstra();
cout<<dis[(k+1)*n]<<'\n';
}
int main()
{
cin.tie(0);
cin.sync_with_stdio(0);
int t=1;
//cin>>t;
while(t--)
work();
return 0;
}
K(luoguP2940)
n ∗ n n*n n∗n的方格上每个格子上都有一个数字(有负数),要求找到一段连续的格子权值和最大(连续的行/列/正对角线/反对角线)
1 ≤ n ≤ 200 1 \le n \le 200 1≤n≤200
思路:
O ( n 3 ) O(n^3) O(n3)能通过本题,枚举即可
#include<bits/stdc++.h>
using namespace std;
#define rep(i,a,b) for(int i=a;i<(b);++i)
#define trav(a,x) for(auto&a : x)
#define all(x) x.begin(),x.end()
#define sz(x) (int) x.size()
typedef pair<int,int> pii;
typedef vector<int> vi;
typedef long long ll;
const int maxn=205;
const int inf=0x3f3f3f3f;
int a[maxn][maxn];
void work()
{
int n;
cin>>n;
rep(i,0,n)
rep(j,0,n)
cin>>a[i][j];
ll ans=-inf;
rep(i,0,n)
rep(j,0,n)
{
ll w=0,x=0,y=0,z=0;
rep(k,0,n)
{
w+=a[(i+k)%n][(j+k)%n];
x+=a[(i+k)%n][j%n];
y+=a[i%n][(j+k)%n];
z+=a[(i-k+n)%n][(j+k)%n];
ans=max(ans,max(max(w,x),max(y,z)));
}
}
cout<<ans<<'\n';
}
int main()
{
cin.tie(0);
cin.sync_with_stdio(0);
int t=1;
//cin>>t;
while(t--)
work();
return 0;
}
L(luoguP1772)
m m m个点 e e e条边的无向带权图,有 n n n天,每一天都要从A点向B点跑一次
每个点都可能有若干天无法经过,而如果在第 i i i天和第 i + 1 i+1 i+1变换了路径,则需要额外 k k k的代价
总成本为$ n 天运输路线长度之和 +k× 改变运输路线的次数。$
1 ≤ n ≤ 100 , 1 ≤ m ≤ 20 , 1 ≤ k ≤ 500 , 1 ≤ e ≤ 200 。 1≤n≤100,1≤m≤20, 1≤k≤500, 1≤e≤200。 1≤n≤100,1≤m≤20,1≤k≤500,1≤e≤200。
思路:
一道嫁接题,最短路+dp
如果我们可以预处理出 [ i , j ] [i,j] [i,j]天不改变路线的最短路径 c [ i ] [ j ] c[i][j] c[i][j]的话,那么显然有dp方程
设 d p [ i ] dp[i] dp[i]为前 i i i天的最小代价,则
d p [ i ] = m i n j = 0 j < i ( d p [ j ] + ( i − j ) ∗ c [ j + 1 ] [ i ] + k ) dp[i]=min^{j<i}_{j=0}(dp[j]+(i-j)*c[j+1][i]+k) dp[i]=minj=0j<i(dp[j]+(i−j)∗c[j+1][i]+k)
而总共有 n 2 n^2 n2个这样的 [ i , j ] [i,j] [i,j],跑一遍最短路的时间是 ( m + e ) l o g 2 e (m+e)log_2^e (m+e)log2e,不会超时
#include<bits/stdc++.h>
using namespace std;
#define rep(i,a,b) for(int i=a;i<=(b);++i)
#define trav(a,x) for(auto&a : x)
#define all(x) x.begin(),x.end()
#define sz(x) (int) x.size()
typedef pair<int,int> pii;
typedef vector<int> vi;
typedef long long ll;
const int maxn=105;
const int inf=0x3f3f3f3f;
struct Edge
{
int to;int dis;
};
vector<Edge> adj[25],con[25];
ll dp[maxn],cost[maxn][maxn];
int d,n,k,m;
struct node
{
int u;ll val;
};
bool operator <(const node &a,const node &b)
{
return a.val>b.val;
}
priority_queue<node> q;
ll dijkstra(vector<bool> vis)
{
vector<ll> dis(n+1);
rep(i,1,n) dis[i]=inf;
dis[1]=0;q.push((node){1,0});
while(!q.empty())
{
node tmp=q.top();q.pop();
int u=tmp.u;
if(vis[u]) continue;
vis[u]=true;
trav(e,adj[u])
{
int v=e.to;
if(vis[v]) continue;
if(dis[u]+e.dis<dis[v])
{
dis[v]=dis[u]+e.dis;
q.push((node){v,dis[v]});
}
}
}
return dis[n];
}
void work()
{
cin>>d>>n>>k>>m;
rep(i,1,m)
{
int x,y,z;
cin>>x>>y>>z;
adj[x].push_back((Edge){y,z});
adj[y].push_back((Edge){x,z});
}
int qu;
cin>>qu;
while(qu--)
{
int x,y,z;
cin>>x>>y>>z;
con[x].push_back((Edge){y,z});
}
rep(i,1,d)
rep(j,i,d)
{
vector<bool> vis(n+1);
rep(u,1,n) vis[u]=false;
rep(u,1,n)
{
trav(p,con[u])
if((i<=p.to&&p.to<=j)||(i<=p.dis&&p.dis<=j)||(p.to<=i&&j<=p.dis)) vis[u]=true;
}
cost[i][j]=dijkstra(vis);
}
rep(i,1,d)
{
dp[i]=cost[1][i]*i;
rep(j,0,i-1)
{
dp[i]=min(dp[i],dp[j]+cost[j+1][i]*(i-j)+k);
}
}
cout<<dp[d]<<'\n';
}
int main()
{
cin.tie(0);
cin.sync_with_stdio(0);
int t=1;
//cin>>t;
while(t--)
work();
return 0;
}