暑假专题训练一:新生专题训练

暑假专题训练一

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 10n1e6,1k26

思路:移动双指针即可,尺取移动的时候留意一下哪个指针每次移动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=0n1AiBn1i

其中 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 m5000,S20000

思路:

注意到要找的两个子串长度相同,那么我们可以考虑枚举一个中心点 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](ij)之间元素的按位或之和

给定一个数 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} 1n1e5,1ai230

思路:由于或运算没有逆元,我们左移双指针的时候不方便一步”撤销“

而这里位运算每一位可以拆开考虑,所以直接双指针维护 [ 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 1n30000,0ai1e9,1Q1e5

思路:

离线+线段树

如果题目没有要求查询不相同的数字之和,那么这就是一个线段树区间查询的板子题

我们考虑,如果一个区间中有一个数字出现了多次,那么我们只需要把其中一个出现的地方不变,其他地方全赋值成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 1n1e5,1m1e6

思路:

一开始看错题了,想着反着按照时间顺序用并查集来维护。

事实上这道题直接模拟的话用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 1n10000,1m50000,1k20

经典分层图最短路,回忆复健了一下,第 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 nn的方格上每个格子上都有一个数字(有负数),要求找到一段连续的格子权值和最大(连续的行/列/正对角线/反对角线)

1 ≤ n ≤ 200 1 \le n \le 200 1n200

思路:

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。 1n1001m20,1k500,1e200

思路:

一道嫁接题,最短路+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]+(ij)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;
}
  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值