“科大讯飞杯”第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,'\n');
}
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\neq 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\n",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\)的乘积,即\(\sum\limits_{k=1}^{n} \sum\limits_{i=1}^{|t|} \sum\limits_{j=1}^{|t|}v_{k}[s_k=substr(t,i,j)]\).
解题思路
根据期望的定义,答案就是\(\frac{\text{乱七八糟}}{27^{m}}\),我们现在考虑 乱七八糟 怎么算。
乱七八糟 就是全部可能的字符串乘以对应的价值,直接计算的复杂度是\(27^{m}\)。我们考虑如何合并。
我们先按长度进行分类,对于同一长度的字符串\(t\),它在\(27^{m}\)的情况中出现的次数都是一样的。这一长度的总价值计算,从得到的字符串\(t\)来考虑显然不现实,我们就经典转换成考虑每个\(s_{i}\)对该长度总价值的贡献。
设当前考虑的字符串长度为\(l\),对于一个长度为\(|s_{i}|\)小于等于\(l\)的串\(s_{i}\),它对字符串\(t\)长度为\(l\)时的总价值贡献就为\(v_{i} \times (l-|s_{i}|+1) \times 26^{l-|s_{i}|}\times \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,'\n');
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,'\n');
}
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\n",sx-1,sy-1);
return;
}
print_st(pre_st[x][y].first,pre_st[x][y].second);
printf("%d %d\n",x-1,y-1);
}
void print_en(int x,int y){
if (x!=ast.x||y!=ast.y) printf("%d %d\n",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\n",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\n",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,'\n');
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\n",ans);
return 0;
}