“科大讯飞杯”第18届上海大学程序设计联赛春季赛暨高校网络友谊赛

鸽了快一个月的题解

A. 组队比赛 (Nowcoder 5278 A)

题目大意

给定(a,b,c,d),俩俩分组,使得两组中数的和的差最小。

解题思路

最小最大一组另外的一组即可。

神奇的代码
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;

int main(void) {
    ios::sync_with_stdio(false); 
    cin.tie(0); cout.tie(0);
    int a[4];
    cin>>a[0]>>a[1]>>a[2]>>a[3];
    sort(a,a+4);
    int ans=abs(a[0]+a[3]-a[1]-a[2]);
    cout<<ans<<endl;
    return 0;
}


B. 每日一报 (Nowcoder 5278 B)

题目大意

符合条件的结构体排序。

解题思路

符合条件的结构体排序。

神奇的代码
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;

const double eps=1e-5;

struct data{
    int ri,num;
    double t;
    bool operator < (const data & a){
        if (ri>a.ri) return true;
        if (ri<a.ri) return false;
        if (t-a.t>eps) return true;
        if (t-a.t<eps) return false;
        if (num<a.num) return true;
        return false;
    }
};

int main(void) {
    ios::sync_with_stdio(false); 
    cin.tie(0); cout.tie(0);
    vector<data> qwq;
    int n;
    cin>>n;
    double tt;
    for(int r,nu,i=1;i<=n;++i){
        cin>>r>>nu>>tt;
        if (tt-38.0<-eps) continue;
        qwq.push_back({r,nu,tt});
    }
    sort(qwq.begin(),qwq.end());
    cout<<qwq.size()<<endl;
    for(auto i:qwq){
        cout<<i.ri<<' '<<i.num<<' '<<fixed<<setprecision(1)<<i.t<<endl;
    }
    return 0;
}


C. 最长非公共子序列 (Nowcoder 5278 C)

题目大意

给定(s_{1},s_{2}),求出最长的非公共子序列的长度,不存在输出(-1)

解题思路

相等即为(-1)否则为长度最大的那个。

神奇的代码
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;

int main(void) {
    ios::sync_with_stdio(false); 
    cin.tie(0); cout.tie(0);
    string s1,s2;
    cin>>s1>>s2;
    if (s1==s2) cout<<"-1"<<endl;
    else cout<<max(s1.size(),s2.size())<<endl;
    return 0;
}


D. 最大字符集 (Nowcoder 5278 D)

题目大意

给定一个(n),求一个集合,满足以下条件:

  • 每个字符串由 0 和 1 组成。
  • 每个字符串长度在 1 到 n 之间,且两两长度不同。
  • 集合中任何一个字符串都不是其他字符串的子串。

最大化集合元素个数,输出任意一种可能的集合情况。

解题思路

构造题。(1,2)特殊处理,其余的最大个数为(n-1),长度从(2)(n),每个元素首尾为(1)中间为(0)

神奇的代码
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;

int main(void) {
    ios::sync_with_stdio(false); 
    cin.tie(0); cout.tie(0);
    int n;
    cin>>n;
    if (n==1){
        cout<<"1"<<endl;
        cout<<"1"<<endl;
    }else if (n==2){
        cout<<"2"<<endl;
        cout<<"1"<<endl;
        cout<<"00"<<endl;
    }else{
        cout<<n-1<<endl;
        for(int i=2;i<=n;++i){
            string s(i,'0');
            s[0]='1';
            s[i-1]='1';
            cout<<s<<endl;
        }
    }
    return 0;
}


E. 美味的序列 (Nowcoder 5278 E)

题目大意

给定一个序列,每次可以从头或尾取一个数,每次取后序列里的所有数减一。问最优的取法,取出来的数的和是多少。

解题思路

很容易发现怎么取结果都是一样的(将减一操作作用在另一个初始全为0的数组),于是答案就是所有数的和减去(frac{n(n-1)}{2})

神奇的代码
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;

int main(void) {
    ios::sync_with_stdio(false); 
    cin.tie(0); cout.tie(0);
    int n;
    cin>>n;
    LL ans=-(LL)(n)*(n-1)/2;
    LL qwq=0;
    while(n--){
        cin>>qwq;
        ans+=qwq;
    }
    cout<<ans<<endl;
    return 0;
}


F. 日期小助手 (Nowcoder 5278 F)

题目大意

给定一个日期,输出该日期后最近的母亲节或父亲节是什么时候。

解题思路

求出当前年和下一年的母亲父亲节日期比较即可。
恰好练一练类。

神奇的代码
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;

struct data{
    int y,m,d;
    bool operator < (const data & a){
        if (y<a.y) return true;
        if (y>a.y) return false;
        if (m<a.m) return true;
        if (m>a.m) return false;
        if (d<a.d) return true;
        if (d>a.d) return false;
        return false;
    }
};


int run[108]={0};

bool check(int y){
    if (y%400==0) return true;
    if (y%100!=0&&y%4==0) return true;
    return false;
}

pair<data,data> solve(int y){
    int x=6-(y-2000+run[y-2000]);
    int xx=3-(y-2000+run[y-2000]);
    while(x<0){
        x+=7;
    }
    while(xx<0){
        xx+=7;
    }
    data a={y,5,x+8};
    data b={y,6,xx+15};
    return (make_pair(a,b));
}


int main(void) {
    ios::sync_with_stdio(false); 
    cin.tie(0); cout.tie(0);
    for(int i=1;i<=105;++i)
        if (check(i+2000)) run[i]++;
    for(int i=1;i<=101;++i) run[i]+=run[i-1];
    int t;
    cin>>t;
    while(t--){
        data noww;
        cin>>noww.y>>noww.m>>noww.d;
        auto qwq=solve(noww.y);
        auto qaq=solve(noww.y+1);
        if (noww<qwq.first){
            cout<<"Mother's Day: May "<<qwq.first.d<<"th, "<<qwq.first.y<<endl;
        }else if (noww<qwq.second){
            cout<<"Father's Day: June "<<qwq.second.d;
            if (qwq.second.d==21) cout<<"st, ";
            else cout<<"th, ";
            cout<<qwq.second.y<<endl;
        }else{
            cout<<"Mother's Day: May "<<qaq.first.d<<"th, "<<qaq.first.y<<endl;
        }
    }
    return 0;
}


G. 血压游戏 (Nowcoder 5278 G)

题目大意

给定一棵树,根的编号是(s)(从(1)开始),每个节点有(a_i)只松鼠,它们不断向根节点移动,且会依次发生以下事件:

  • 如果一个节点上有 2 只或 2 只以上的松鼠,他们会打架,然后这个节点上松鼠的数量会减少 1;
  • 根节点的所有松鼠移动到地面,位于地面上的松鼠不会再打架;
  • 所有松鼠同时朝它们的父节点移动。

问最终有多少只松鼠到达地面。

解题思路

容易发现不同深度之间的松鼠互不干扰,所以我们每次就针对同一深度的松鼠考虑。

(dp[i])表示到达节点(i)的松鼠数量,很容易可以转移,但每个深度的求解复杂度是(O(n)),最坏情况即链为(O(n^{2}))会炸。

但我们发现松鼠在往根跑的时候好多情况是一样的(即减少的松鼠数为深度的减少数),只是在多处松鼠的所谓的"汇聚点"即(LCA)处会发生变化。于是我们可以可以把树压缩,保留那些关键的转移点(即(LCA)),去掉那些可以预知变化的点(即起始点到LCA间的点)。

其实这就是要建一棵虚树,用栈维护当前建的一条从根节点开始的链。虚树(DP)

最终复杂度即为(O(n))

神奇的代码
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;

template <typename T>
void read(T &x) {
    int s = 0, c = getchar();
    x = 0;
    while (isspace(c)) c = getchar();
    if (c == 45) s = 1, c = getchar();
    while (isdigit(c)) x = (x << 3) + (x << 1) + (c ^ 48), c = getchar();
    if (s) x = -x;
}

template <typename T>
void write(T x, char c = ' ') {
    int b[40], l = 0;
    if (x < 0) putchar(45), x = -x;
    while (x > 0) b[l++] = x % 10, x /= 10;
    if (!l) putchar(48);
    while (l) putchar(b[--l] | 48);
    putchar(c);
}

struct data{
    int deep,t,id;

    bool operator < (const data & a) const{
        if (deep<a.deep) return true;
        if (deep>a.deep) return false;
        if (t<a.t) return true;
        return false;
    }
};

const int N=2e5+8;

vector<data> po;

vector<int> edge[N];

int up[N][32];

LL dp[N];

int deep[N];

int n,s,t;

LL cnt[N];

LL ans;

void DFS(int u,int fa){
    deep[u]=deep[fa]+1;
    po.push_back({deep[u],++t,u});
    up[u][0]=fa;
    for(auto v:edge[u]){
        if (v==fa) continue;
        DFS(v,u);
    }
}

int lca(int u,int v){
    if (u==v) return u;
    if (deep[u]<deep[v]) swap(u,v);
    for(int i=31;i>=0;--i){
        if (deep[up[u][i]]>=deep[v])
            u=up[u][i];
    }
    if (u==v) return u;
    for(int i=31;i>=0;--i){
        if (up[u][i]!=up[v][i]){
            u=up[u][i];
            v=up[v][i];
        }
    }
    return up[u][0];
}

int st[N],top;

int head[N],to[N*2],nxt[N*2],num;

void add(int u,int v){
    num++;
    nxt[num]=head[u];
    to[num]=v;
    head[u]=num;
}

void DP(int u,int fa){
    for(int v,i=head[u];i;i=nxt[i]){
        v=to[i];
        if (v==fa) continue;
        DP(v,u);
        if (dp[v]) dp[u]+=max(1ll,dp[v]-deep[v]+deep[u]+1);
        dp[v]=0;
        head[u]=0;
    }
    if (dp[u]) dp[u]=max(1ll,dp[u]-1);
}

void solve(int l,int r){
    num=0;
    top=0;
    st[top]=s;
    int la=s;
    for(int i=l;i<=r;++i){
        dp[po[i].id]=cnt[po[i].id];
        int cur=po[i].id;
        int fa=lca(la,cur);
        while(top>0&&deep[st[top-1]]>=deep[fa]){
            add(st[top-1],st[top]);
            --top;
        }
        if (st[top]!=fa){
            add(fa,st[top]);
            st[top]=fa;
        }
        st[++top]=cur;
        la=cur;
    }
    while(top>0){
        add(st[top-1],st[top]);
        --top;
    }
    DP(s,s);
    ans+=dp[s];
    dp[s]=0;
}

int main(){
    read(n);
    read(s);
    for(int i=1;i<=n;++i) read(cnt[i]);
    for(int u,v,i=1;i<n;++i){
        read(u);
        read(v);
        edge[u].push_back(v);
        edge[v].push_back(u);
    }
    DFS(s,s);
    for(int i=1;i<=31;++i)
        for(int j=1;j<=n;++j)
            up[j][i]=up[up[j][i-1]][i-1];
    sort(po.begin(),po.end());
    int l=1;
    if (cnt[po[0].id]) ans=max(1ll,cnt[po[0].id]-1);
    for(int i=2;i<n;++i){
        if (po[i].deep!=po[i-1].deep){
            solve(l,i-1);
            l=i;
        }
    }
    solve(l,n-1);
    write(ans,'
');
}


H. 纸牌游戏 (Nowcoder 5278 H)

题目大意

给定长度为(n)的数字串,每个数字(0)(9),要求从中选取(k)个数字组成一个非负整数,使得它可以被(3)整除且最大。

解题思路

首先对数字串中的数字从大到小排序,然后设(dp[i][j][k])表示当前第(i)个数,还可以取(j)个数,当前选的数组成的数对(3)取模为(k),后继是否存在可行方案(记忆化搜索,含义似乎难以描述qwq),转移就看这第(i)个数选或不选两种方式。时间复杂度(O(nk))爆了空间和时间。

稍加思索会发现我们定义的状态里有好多***冗余重复 ***的状态,如(9999),我们会考虑了多次选(1)(9)(2)(9)的情况或(3)(9)的情况,于是我们改变定义方式为(dp[i][j][k])为当前考虑选择数字(i)(从9开始考虑),还有(j)个可以选,对(3)取模为(k)后继是否存在可行方案,转移即可。时间复杂度(O(9n*3))

特判全是(0)(k eq 1)的情况。

多组数据,不能每组(memset),于是玄学操作——自定义真假未定义。

神奇的代码
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
template <typename T>
void read(T &x) {
    int s = 0, c = getchar();
    x = 0;
    while (isspace(c)) c = getchar();
    if (c == 45) s = 1, c = getchar();
    while (isdigit(c)) x = (x << 3) + (x << 1) + (c ^ 48), c = getchar();
    if (s) x = -x;
}

template <typename T>
void write(T x, char c = ' ') {
    int b[40], l = 0;
    if (x < 0) putchar(45), x = -x;
    while (x > 0) b[l++] = x % 10, x /= 10;
    if (!l) putchar(48);
    while (l) putchar(b[--l] | 48);
    putchar(c);
}

const int N=1e5+8;

char s[N];

int cc[10];

int tt=0;

int dp[10][N][3];

int sum[10];

bool DFS(int num,int k,int mo,int cnt[]){
    if (k==0) return mo==0;
    if (num<0) return false;
    if (k>sum[num]) return false; //可行性剪枝
    if (dp[num][k][mo]>tt) return dp[num][k][mo]==tt+2;
    for(int i=min(cnt[num],k);i>=0;--i){
        if (DFS(num-1,k-i,(mo+num*i)%3,cnt)){
            cc[num]=i;
            dp[num][k][mo]=tt+2;
            return true;
        }
    }
    dp[num][k][mo]=tt+1;
    return false;
}

int main(void) {
    int kase; read(kase);
    for (int ii = 1; ii <= kase; ii++){
        scanf("%s",s);
        int k;
        read(k);
        int cnt[10]={0};
        int len=strlen(s);
        for(int i=0;i<len;++i)
            cnt[s[i]-'0']++;
        for(int i=0;i<10;++i) cc[i]=0;
        sum[0]=cnt[0];
        for(int i=1;i<10;++i) sum[i]=cnt[i]+sum[i-1];
        bool qwq=DFS(9,k,0,cnt);
        if (qwq&&(cc[0]!=k||k==1)){
            char ans[k+1]={0};
            int cr=0;
            for(int i=9;i>=0;--i){
                while(cc[i]){
                    ans[cr++]=i+'0';
                    cc[i]--;
                }
            }
            printf("%s
",ans);
        }else puts("-1");
        tt+=3;
    }
    return 0;
}


I. 古老的打字机 (Nowcoder 5278 I)

题目大意

给定(n)个串(s_{i})以及对应的价值(v_{i}),现在会敲击打字机(m)次,每次会等概率的敲击出一个小写字母或者退格键,退格键的作用是将当前得到的字符串的最后一个字母删除,当然如果当前是空串,则无事发生。问得到的字符串(t)的价值的期望的(27^{m})倍是多少。

字符串(t)的价值定义为:每个串(s_i)在得到的串t中的出现次数(c_i)与价值(v_i)的乘积,即(sumlimits_{k=1}^{n} sumlimits_{i=1}^{|t|} sumlimits_{j=1}^{|t|}v_{k}[s_k=substr(t,i,j)]).

解题思路

根据期望的定义,答案就是(frac{ ext{乱七八糟}}{27^{m}}),我们现在考虑 乱七八糟 怎么算。

乱七八糟 就是全部可能的字符串乘以对应的价值,直接计算的复杂度是(27^{m})。我们考虑如何合并。

我们先按长度进行分类,对于同一长度的字符串(t),它在(27^{m})的情况中出现的次数都是一样的。这一长度的总价值计算,从得到的字符串(t)来考虑显然不现实,我们就经典转换成考虑每个(s_{i})对该长度总价值的贡献。

设当前考虑的字符串长度为(l),对于一个长度为(|s_{i}|)小于等于(l)的串(s_{i}),它对字符串(t)长度为(l)时的总价值贡献就为(v_{i} imes (l-|s_{i}|+1) imes 26^{l-|s_{i}|} imes dfrac{dp[m][l]}{26^{l}})

(dp[m][l])表示敲击(m)次,得到串(t)长度为(l)的方案数。这个一开始预处理即可得到。

神奇的代码
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
template <typename T>
void read(T &x) {
    int s = 0, c = getchar();
    x = 0;
    while (isspace(c)) c = getchar();
    if (c == 45) s = 1, c = getchar();
    while (isdigit(c)) x = (x << 3) + (x << 1) + (c ^ 48), c = getchar();
    if (s) x = -x;
}

template <typename T>
void write(T x, char c = ' ') {
    int b[40], l = 0;
    if (x < 0) putchar(45), x = -x;
    while (x > 0) b[l++] = x % 10, x /= 10;
    if (!l) putchar(48);
    while (l) putchar(b[--l] | 48);
    putchar(c);
}

const LL mo=1e9+7;

const int N=1e3+8;

int n,m;

int len[N];

LL val[N];

LL ans;

LL dp[N][N];

LL p26[N];

char s[N];

LL qpower(LL a,LL b){
    LL qwq=1;
    while(b){
        if (b&1) qwq=qwq*a%mo;
        b>>=1;
        a=a*a%mo;
    }
    return qwq;
}

LL inv(LL x){
    return qpower(x,mo-2);
}

int main(void) {
    read(n);
    read(m);
    for(int i=1;i<=n;++i){
        scanf("%s",s);
        len[i]=strlen(s);
        read(val[i]);
    }
    p26[0]=1;
    for(int i=1;i<=1004;++i) p26[i]=p26[i-1]*26ll%mo;
    dp[0][0]=1;
    for(int i=1;i<=m;++i)
        for(int j=0;j<=i;++j){
            if (j==0) dp[i][j]=(dp[i-1][j]+dp[i-1][j+1])%mo;
            else if (j>=i-1) dp[i][j]=dp[i-1][j-1]*26ll%mo;
            else dp[i][j]=(dp[i-1][j-1]*26ll%mo+dp[i-1][j+1])%mo;
        }
    LL tmp=1;
    LL qwq=1;
    for(int i=0;i<=m;++i){
        for(int j=1;j<=n;++j){
            if (len[j]>i) continue;
            ans=(ans+(i-len[j]+1ll)*p26[i-len[j]]%mo*val[j]%mo*dp[m][i]%mo*qwq%mo)%mo;
        }
        tmp=tmp*26%mo;
        qwq=inv(tmp);
    }
    write(ans,'
');
    return 0;
}


J. 能到达吗 (Nowcoder 5278 J)

题目大意

给定一张(n*m)的图,左上角坐标((1,1)),右下角坐标((n,m)),有(k)个障碍物,第(i)个障碍物坐标为((x_{i},y_{i}))。你可以在空地上上下左右走动,但不能跳出边界。问俩俩能到达的无序点对(即从一个点可以到达另外一个点)有多少。答案模(1e9+7)

解题思路

很显然,如果一个连通块有(cnt)个点,那么这个连通块对答案的贡献就为(dfrac{cnt*(cnt-1)}{2}+cnt),我们考虑如何求出每个连通块中的点数。

由于(n,m leq 2 cdot 10^{5}),而(k leq 10^{6}),我们用一根扫描线对这张图从上往下扫描,当前第(i)行,在该行的障碍物把该行分成了若干条互不相交的线段,这些线段归属于哪个连通块我们可以用并查集维护。

然后考虑第(i+1)行,在该行的障碍物也把该行分成了若干条互不相交的线段,现在我们对第(i)行和第(i+1)行的线段进行 合并 ,这里的 合并 指的是连通块的合并,即如果这两行中的两条线段是相交的,则它们应属于同一连通块,合并完后我们拿第(i+1)行的线段与(i+2)行的线段继续合并即可。合并的过程就是一个模拟的过程。

对于该行没有障碍物的,则视为一条线段,如果有连续若干行无障碍物,这我们可以把这若干行压成一行,视为一条线段,与前一行或后一行合并。

特殊处理无障碍物的情况。

神奇的代码
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
template <typename T>
void read(T &x) {
    int s = 0, c = getchar();
    x = 0;
    while (isspace(c)) c = getchar();
    if (c == 45) s = 1, c = getchar();
    while (isdigit(c)) x = (x << 3) + (x << 1) + (c ^ 48), c = getchar();
    if (s) x = -x;
}

template <typename T>
void write(T x, char c = ' ') {
    int b[40], l = 0;
    if (x < 0) putchar(45), x = -x;
    while (x > 0) b[l++] = x % 10, x /= 10;
    if (!l) putchar(48);
    while (l) putchar(b[--l] | 48);
    putchar(c);
}

const LL mo=1e9+7;

const int N=5e6+8;

LL n,m,k;

int f[N],tot;

LL cnt[N];

pair<int,int> po[N];

struct data{
	int l,r,id;
};

vector<data> seg[2];

int cur;

LL ans;

LL inv2;

int findfa(int x){
	if (f[x]==x) return x;
	else return f[x]=findfa(f[x]);
}

bool check(data & a,data & b){
	return a.r>=b.l&&a.l<=b.r;
}

void unionn(){
	if (seg[cur^1].empty()) return;
	int l=0;
	for(auto & i:seg[cur]){
		while(true){
			if (l<(int)seg[cur^1].size()&&check(i,seg[cur^1][l])){
				int fa=findfa(i.id);
				int fb=findfa(seg[cur^1][l].id);
				if (fa!=fb){
					f[fa]=fb;
					cnt[fb]=(cnt[fb]+cnt[fa])%mo;
				}
				++l;
			}else{
                if (l==(int)seg[cur^1].size()||seg[cur^1][l].l>i.r){
                    l=max(l-1,0);
                    break;
                }else ++l;
			}
		}
	}
}

void work(int u,int d){
    if (u>d) return;
	cur^=1;
	while(!seg[cur].empty()) seg[cur].pop_back();
	++tot;
	f[tot]=tot;
	cnt[tot]=(LL)(d-u+1ll)*m%mo;
	seg[cur].push_back({1,(int)m,tot});
	unionn();
}

void solve(int l,int r){
    if (l>r) return;
	int la=0;
	cur^=1;
    while(!seg[cur].empty()) seg[cur].pop_back();
	for(int i=l;i<=r;++i){
		if (po[i].second-la>1){
			++tot;
			f[tot]=tot;
			cnt[tot]=po[i].second-la-1;
			seg[cur].push_back({la+1,po[i].second-1,tot});
		}
		la=po[i].second;
	}
	if (m>la){
		++tot;
		f[tot]=tot;
		cnt[tot]=m-la;
		seg[cur].push_back({la+1,(int)m,tot});
	}
	unionn();
}

LL qpower(LL a,LL b){
	LL qwq=1;
	while(b){
		if (b&1) qwq=qwq*a%mo;
		b>>=1;
		a=a*a%mo;
	}
	return qwq;
}

LL inv(LL x){
	return qpower(x,mo-2);
}

int main(void) {
    int kase; read(kase);
    inv2=inv(2);
    for (int ii = 1; ii <= kase; ii++){
        tot=0;
        ans=0;
        while(!seg[0].empty()) seg[0].pop_back();
        while(!seg[1].empty()) seg[1].pop_back();
        read(n);
        read(m);
        read(k);
        if (k==0) ans=((n*m%mo)*((n*m-1ll)%mo)%mo*inv2%mo+n*m%mo)%mo;
        else{
            for(int i=1;i<=k;++i) read(po[i].first),read(po[i].second);
            sort(po+1,po+1+k);
            int la=0;
            int l=1;
            work(1,po[1].first-1);
            la=po[1].first;
            for(int i=2;i<=k;++i){
                if (po[i].first!=la){
                    solve(l,i-1);
                    if (po[i].first-la>1) work(la+1,po[i].first-1);  //有空行
                    l=i;
                    la=po[i].first;
                }
            }
            solve(l,k);
            la=po[k].first;
            if (n>la){
                work(la+1,n);
            }
            for(int i=1;i<=tot;++i){
                if (f[i]==i){
                    ans=(ans+cnt[i]*(cnt[i]-1)%mo*inv2%mo+cnt[i])%mo;
                }
            }
        }
        write(ans,'
');
        }
    return 0;
}


K. 迷宫 (Nowcoder 5278 K)

题目大意

图上一些地方有障碍物(X),有一个地方是起点(S),有一个地方是终点(T)。现要求从起点移动到终点,一次操作可以上下左右移动一格,但不能超出边界。同时给定一个整数(d),可以使用一次技能,即从当前点(a)移动到另一点(b),两点的切比雪夫(横坐标差与纵坐标差的最大值)距离不得大于(d),使用一次技能算一步操作。问从起点到终点的最小操作次数,并输出一种可行移动方案。

解题思路

切比雪夫距离对应的就是一个正方形的范围。

我们从起点开始BFS,再从终点开始BFS,记录每个点到起点和终点的距离。

然后再枚举一个边长为(d+1)的正方形范围,设这个范围中到起点的最小值为(s)和到终点的最小值(t),则在该范围使用技能后的最小操作次数就是(s+t+1),对所有范围取最小值即为答案。

至于维护二维正方形的最小值,用两次单调队列即可。先对行的每个数维护它右边(d)个的最小值,再对每一列,用这个最小值,维护每行向下(d)个数的最小值。

至于输出方案,再记录前驱和取得的最小值的位置吧。

特殊处理(d=0)的情况。

神奇的代码
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
template <typename T>
void read(T &x) {
    int s = 0, c = getchar();
    x = 0;
    while (isspace(c)) c = getchar();
    if (c == 45) s = 1, c = getchar();
    while (isdigit(c)) x = (x << 3) + (x << 1) + (c ^ 48), c = getchar();
    if (s) x = -x;
}

template <typename T>
void write(T x, char c = ' ') {
    int b[40], l = 0;
    if (x < 0) putchar(45), x = -x;
    while (x > 0) b[l++] = x % 10, x /= 10;
    if (!l) putchar(48);
    while (l) putchar(b[--l] | 48);
    putchar(c);
}

const int N=2e3+8;

const int dx[4]={0,0,1,-1};

const int dy[4]={-1,1,0,0};

struct data{
    int val;
    int x,y;

    bool operator < (const data & a) const{
        return val<a.val;
    }

    operator int(){
        return val;
    }
}st[N][N],en[N][N],ast,aen;

char ma[N][N];

int n,m,d;

int ans;

int sx,sy,ex,ey;

pair<int,int> pre_st[N][N],pre_en[N][N];

void BFS_st(){
    st[sx][sy].val=0;
    pre_st[sx][sy]=make_pair(sx,sy);
    queue<pair<int,int>> team;
    team.push(make_pair(sx,sy));
    while(!team.empty()){
        auto u=team.front();
        team.pop();
        for(int i=0;i<4;++i){
            auto v=make_pair(u.first+dx[i],u.second+dy[i]);
            if (ma[v.first][v.second]=='X') continue;
            if (st[v.first][v.second]>st[u.first][u.second]+1){
                st[v.first][v.second].val=st[u.first][u.second].val+1;
                team.push(v);
                pre_st[v.first][v.second]=u;
            }
        }
    }
}

void BFS_en(){
    en[ex][ey].val=0;
    pre_en[ex][ey]=make_pair(ex,ey);
    queue<pair<int,int>> team;
    team.push(make_pair(ex,ey));
    while(!team.empty()){
        auto u=team.front();
        team.pop();
        for(int i=0;i<4;++i){
            auto v=make_pair(u.first+dx[i],u.second+dy[i]);
            if (ma[v.first][v.second]=='X') continue;
            if (en[v.first][v.second]>en[u.first][u.second]+1){
                en[v.first][v.second].val=en[u.first][u.second].val+1;
                team.push(v);
                pre_en[v.first][v.second]=u;
            }
        }
    }
}

void pre_s(){
    deque<data> team;
    for(int i=1;i<=n;++i){
        for(int j=1;j<=min(m,d+1);++j){
            while(!team.empty()&&team.back()>=st[i][j]) team.pop_back();
            team.push_back(st[i][j]);
        }
        st[i][1]=team.front();
        for(int j=min(m,d+1)+1;j<=m;++j){
            while(!team.empty()&&team.front().y<j-d) team.pop_front();
            while(!team.empty()&&team.back()>=st[i][j]) team.pop_back();
            team.push_back(st[i][j]);
            st[i][j-d]=team.front();

        }
        while(!team.empty()) team.pop_back();
    }
    for(int i=1;i<=max(1,m-d);++i){
        for(int j=1;j<=min(n,d+1);++j){
            while(!team.empty()&&team.back()>=st[j][i]) team.pop_back();
            team.push_back(st[j][i]);
        }
        st[1][i]=team.front();
        for(int j=min(n,d+1)+1;j<=n;++j){
            while(!team.empty()&&team.front().x<j-d) team.pop_front();
            while(!team.empty()&&team.back()>=st[j][i]) team.pop_back();
            team.push_back(st[j][i]);
            st[j-d][i]=team.front();
        }
        while(!team.empty()) team.pop_back();
    }
}

void pre_e(){
    deque<data> team;
    for(int i=1;i<=n;++i){
        for(int j=1;j<=min(m,d+1);++j){
            while(!team.empty()&&team.back()>=en[i][j]) team.pop_back();
            team.push_back(en[i][j]);
        }
        en[i][1]=team.front();
        for(int j=min(m,d+1)+1;j<=m;++j){
            while(!team.empty()&&team.front().y<j-d) team.pop_front();
            while(!team.empty()&&team.back()>=en[i][j]) team.pop_back();
            team.push_back(en[i][j]);
            en[i][j-d]=team.front();

        }
        while(!team.empty()) team.pop_back();
    }
    for(int i=1;i<=max(1,m-d);++i){
        for(int j=1;j<=min(n,d+1);++j){
            while(!team.empty()&&team.back()>=en[j][i]) team.pop_back();
            team.push_back(en[j][i]);
        }
        en[1][i]=team.front();
        for(int j=min(n,d+1)+1;j<=n;++j){
            while(!team.empty()&&team.front().x<j-d) team.pop_front();
            while(!team.empty()&&team.back()>=en[j][i]) team.pop_back();
            team.push_back(en[j][i]);
            en[j-d][i]=team.front();
        }
        while(!team.empty()) team.pop_back();
    }
}

void print_st(int x,int y){
    if (x==sx&&y==sy){
        printf("%d %d
",sx-1,sy-1);
        return;
    }
    print_st(pre_st[x][y].first,pre_st[x][y].second);
    printf("%d %d
",x-1,y-1);
}

void print_en(int x,int y){
    if (x!=ast.x||y!=ast.y) printf("%d %d
",x-1,y-1);
    if (x==ex&&y==ey) return;
    int tmp=x;
    x=pre_en[tmp][y].first;
    y=pre_en[tmp][y].second;
    while(x!=ex||y!=ey){
        printf("%d %d
",x-1,y-1);
        tmp=x;
        x=pre_en[tmp][y].first;
        y=pre_en[tmp][y].second;
    }
    if (x!=ast.x||y!=ast.y) printf("%d %d
",x-1,y-1);
}

int main(){
    read(n);
    read(m);
    read(d);
    for(int i=1;i<=n;++i) scanf("%s",ma[i]+1);
    for(int i=1;i<=n;++i) ma[i][0]=ma[i][m+1]='X';
    for(int i=1;i<=m;++i) ma[0][i]=ma[n+1][i]='X';
    for(int i=1;i<=n;++i) for(int j=1;j<=m;++j){
        if (ma[i][j]=='S'){ sx=i; sy=j;}
        if (ma[i][j]=='T'){ ex=i; ey=j;}
        st[i][j]=en[i][j]={1000000007,i,j};
    }
    BFS_st();
    BFS_en();
    ans=1e9+7;
    if (d==0){
        for(int i=1;i<=n;++i) 
            for(int j=1;j<=m;++j) 
                if (st[i][j]+en[i][j]<ans){
                    ans=st[i][j]+en[i][j];
                    ast=st[i][j];
                    aen=en[i][j];
                }
    }else{
        pre_s();
        pre_e();
        for(int i=1;i<=max(1,n-d);++i)
            for(int j=1;j<=max(1,m-d);++j)
                if (st[i][j]+en[i][j]+1<ans){
                    ans=st[i][j]+en[i][j]+1;
                    ast=st[i][j];
                    aen=en[i][j];
                }
    }
    if (ans==1000000007) ans=-1;
    write(ans,'
');
    if (ans!=-1){
        print_st(ast.x,ast.y);
        print_en(aen.x,aen.y);
    }
    return 0;
}


L. 动物森友会 (Nowcoder 5278 L)

题目大意

一周七天,一周的某些天可以执行特定的事件,一天最多执行(e)次事件,同一个事件可以一天可以执行多次。

现在列出要执行的某些事件和且执行该事件的次数,问执行完这些事件所需的最小天数。

解题思路

很显然,当天数足够大时,一定能执行完所有事件。

天数对决策的可行具有单调性,于是我们二分天数,对于一个特定的天数,一周的某些天的执行次数是一定的,我们的任务就是分配这些执行次数,该去执行哪些事件,执行几次,这是带有反悔性质的抉择,跑一遍网络流即可。

神奇的代码
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
template <typename T>
void read(T &x) {
    int s = 0, c = getchar();
    x = 0;
    while (isspace(c)) c = getchar();
    if (c == 45) s = 1, c = getchar();
    while (isdigit(c)) x = (x << 3) + (x << 1) + (c ^ 48), c = getchar();
    if (s) x = -x;
}

template <typename T>
void write(T x, char c = ' ') {
    int b[40], l = 0;
    if (x < 0) putchar(45), x = -x;
    while (x > 0) b[l++] = x % 10, x /= 10;
    if (!l) putchar(48);
    while (l) putchar(b[--l] | 48);
    putchar(c);
}

const int N=2e4+8;

const int INF=1e9+7;

int head[N],nxt[N*2],to[N*2],team[N*2],dis[N*2];

LL b_flow[N*2],flow[N*2];

int n,e,st,en,num;

LL sum;

void add(int u, int v, int w) {
	num++;
	nxt[num] = head[u];
	to[num] = v;
	flow[num] = w;
	head[u] = num;
	num++;
	nxt[num] = head[v];
	to[num] = u;
	flow[num] = 0;
	head[v] = num;
}

bool BFS() {
	int l = 0, r = 1;
	team[1] = st;
	memset(dis, 0, sizeof(dis));
	dis[st] = 1;
	while (l < r) {
		int u = team[++l];
		for (int v, i = head[u]; i; i = nxt[i]) {
			v = to[i];
			if (dis[v] == 0 && flow[i]) {
				dis[v] = dis[u] + 1;
				team[++r] = v;
			}
		}
	}
	if (dis[en]) return true;
	else return false;
}

LL DFS(int u, LL f) {
	if (u == en) return f;
	LL qwq = 0, tmp = 0;
	for (int v, i = head[u]; i; i = nxt[i]) {
		v = to[i];
		if (dis[v] == dis[u] + 1 && flow[i]) {
			qwq = DFS(v, min(f - tmp, flow[i]));
			flow[i] -= qwq;
			flow[i ^ 1] += qwq;
			tmp += qwq;
			if (tmp == f) return tmp;
		}
	}
	return tmp;
}

bool check(int x){
    for(int i=2;i<=num;++i) flow[i]=b_flow[i];
    for(int i=head[st];i;i=nxt[i])
        flow[i]=(LL)((x-1)/7+((x-1)%7>=to[i]-1))*(LL)e;
    LL cnt=0;
    while(BFS()){
        cnt+=DFS(st,INF);
    }
    return cnt==sum;
}

int main(void) {
    read(n);
    read(e);
    num=1;
    st=0;
    sum=0;
    en=n+8;
    for(int i=1;i<=7;++i)
        add(st,i,0);
    for(int c,m,i=1;i<=n;++i){
        read(c);
        read(m);
        sum+=c;
        add(i+7,en,c);
        for(int v,j=1;j<=m;++j){
            read(v);
            add(v,i+7,INF);
        }
    }
    for(int i=2;i<=num;++i)
        b_flow[i]=flow[i];
    int l=0,r=(sum+1)*8;
    int ans=0;
    while(l+1<r){
        int mid=(l+r)>>1;
        if (check(mid)) ans=mid,r=mid;
        else l=mid;
    }
    printf("%d
",ans);
    return 0;  
}


原文地址:https://www.cnblogs.com/Lanly/p/12865951.html