2024牛客暑期多校训练营3
Preface
又被隔壁干烂了,这场最抽象的是三个人开局被 A 卡的死去活来,一直到中期的时候才以 WA 三发的代价过了这个题
封榜后徐神狠狠发力连过两题,使得最后勉强只被打出 \(n+1\) 而不是 \(n+2\),鉴定为我是纯纯的飞舞
Bridging the Gap 2
首先不难发现过程一定是先进行 \(T=\lceil\frac{n-R}{R-L}\rceil\) 次来回操作,每次带 \(R\) 个人过去,再带 \(L\) 个人回来;最后一轮直接带 \(R\) 个人过去
考虑将每个人的体力分为两部分,一部分为将自己运过去需要的;另一部分是多余的可以帮着运送别人的,显然后者就是 \(\lfloor\frac{h_i-1}{2}\rfloor\)
但由于多出的体力超出 \(T\) 的部分没有意义,因此我们求出 \(\sum_{i=1}^n \min(T,\lfloor\frac{h_i-1}{2}\rfloor)\),将这个值与 \(T\times L\) 做比较即可
#include <bits/stdc++.h>
#define int long long
using namespace std;
signed main(void) {
std::ios::sync_with_stdio(false); cin.tie(0);
int n, l, r;
std::cin >> n >> l >> r;
std::vector<int> h(n);
for(auto &h: h) std::cin >> h, h = (h - 1) >> 1;
std::sort(h.begin(), h.end());
int res = n - r;
int tot = (n - r + (r - l) - 1) / (r - l);
int sum = 0;
for (int i=0; i<n; ++i){
sum += min(tot, h[i]);
}
if (sum >= tot*l) cout << "YES\n";
else cout << "NO\n";
return 0;
}
Crash Test
手玩一下会发现这个撞击的过程很像求 \(\gcd\),因此不难发现最后等价于用一个 \(\gcd(a_1,a_2,\dots,a_n)\) 的东西去撞墙
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int INF = (int)1e18+5;
int n, D,x,g ;
signed main(){
ios::sync_with_stdio(0); cin.tie(0);
cin>>n>>D; int g=0;
for (int i=1;i<=n;++i) cin>>x,g=__gcd(g,x);
return printf("%lld",min(D%g,g-D%g)),0;
}
Delete 4 edges
不可做题,弃疗
Dominoes!
很有意思的构造题
首先要注意如果每个块的两个数都不相同,则我们存在一种很 trivial 的构造方案
即先随便放一个骨牌,然后接下来每次放的时候如果按照原顺序不会造成冲突就直接放,否则翻转当前骨牌一定可以放下
现在就考虑存在同色骨牌时怎么操作,手玩一下会发现可以贪心地把出现次数多的同色骨牌和其它同色骨牌合并,每次取出出现次数最多的两种一起放置即可
注意当存在一种同色骨牌出现次数很多时,就需要用其它的异色骨牌填到它们中间,如果找不到这样的骨牌就无解了
否则可以将原问题转化为只有异色骨牌的问题,就很容易处理了
#include<cstdio>
#include<iostream>
#include<utility>
#include<queue>
#include<vector>
#include<map>
#define RI register int
#define CI const int&
#define fi first
#define se second
using namespace std;
typedef pair <int,int> pi;
const int N=200005;
int n,x[N],y[N],used[N]; vector <pi> ans; map <int,int> rst;
int main()
{
RI i; priority_queue <pi> hp;
for (scanf("%d",&n),i=1;i<=n;++i)
{
scanf("%d%d",&x[i],&y[i]);
if (x[i]==y[i]) ++rst[x[i]],used[i]=1;
}
for (auto [val,cnt]:rst) hp.push(pi(cnt,val));
while (hp.size()>=2)
{
auto [mxc,mxv]=hp.top(); hp.pop();
auto [smxc,smxv]=hp.top(); hp.pop();
ans.push_back(pi(mxv,mxv));
ans.push_back(pi(smxv,smxv));
if (mxc-1>0) hp.push(pi(mxc-1,mxv));
if (smxc-1>0) hp.push(pi(smxc-1,smxv));
}
if (hp.size()==1)
{
auto [cnt,val]=hp.top();
if (ans.empty()||val!=ans.back().se)
ans.push_back(pi(val,val)),--cnt;
vector <int> vec; for (i=1;i<=n;++i)
if (!used[i]&&val!=x[i]&&val!=y[i]) vec.push_back(i);
if ((int)vec.size()<cnt) return puts("No"),0;
for (i=1;i<=cnt;++i)
{
int id=vec.back(); vec.pop_back(); used[id]=1;
ans.push_back(pi(x[id],y[id]));
ans.push_back(pi(val,val));
}
}
for (i=1;i<=n;++i) if (!used[i])
{
if (!ans.empty()&&ans.back().se==x[i]) swap(x[i],y[i]);
ans.push_back(pi(x[i],y[i]));
}
puts("Yes");
for (auto [x,y]:ans) printf("%d %d\n",x,y);
return 0;
}
Malfunctioning Typewriter
首先可以将所有串建出一个 Trie 树,并统计出每个子树内终止节点的数量
假设当前处于根节点,左子树有 \(x\) 个终止节点,右子树有 \(y\) 个终止节点
由于最后匹配的过程一定会在这个点选择 \(x+y\) 次,因此不妨把贡献拆开单独考虑,计算出每个点选择的总贡献然后相乘即可
而最优的选择策略可以用 DP 预处理出来,总复杂度 \(O(nm)\)
#include <bits/stdc++.h>
using real = double;
real dp[1001][1001];
int go[1'000'001][2], O = 1, siz[1'000'001];
int main() {
std::ios::sync_with_stdio(false);
int n, m;
real p;
std::cin >> n >> m >> p;
dp[0][0] = 1;
for(int i = 1; i <= 1000; ++i) dp[0][i] = dp[i][0] = dp[i - 1][0] * p;
for(int i = 1; i <= 1000; ++i) for(int j = 1; j <= 1000; ++j) {
dp[i][j] = std::max(
p * dp[i - 1][j] + (1. - p) * dp[i][j - 1],
(1. - p) * dp[i - 1][j] + p * dp[i][j - 1]
);
}
while(n--) {
std::string s; std::cin >> s;
int cur = 1;
for(auto c: s) {
int u = c - '0';
if(!go[cur][u]) go[cur][u] = ++O;
siz[cur = go[cur][u]] += 1;
}
}
real ans = 1;
for(int i = 1; i <= O; ++i) ans *= dp[siz[go[i][0]]][siz[go[i][1]]];
std::cout << std::fixed << std::setprecision(15);
std::cout << ans << char(10);
return 0;
}
MMR Double Down Tokens
神秘计数题,弃疗
Pythagorathean Transformation
神秘数论题,弃疗
Rectangle Intersection
前面我和祁神出了一堆假做法,后面还得靠徐神看一眼秒了
考虑把一个矩形拆成四个边界,对于每个格点,考虑维护出其上/左/下/右四个方向上距离最近的边界,这个实现时可以用扫描线+一阶差分处理
最后遍历每个格子,计算其对应的四个边界对答案的贡献,样例给了个很强的 Corner Case,就是必须对覆盖了至少一次的格子计算答案,否则会把贡献算小
总复杂度 \(O(nm+k)\)
#include <bits/stdc++.h>
constexpr int $n = 2002;
int n, m, k;
int S[$n][$n];
int U[$n][$n], D[$n][$n], L[$n][$n], R[$n][$n];
int u[$n][$n], d[$n][$n], l[$n][$n], r[$n][$n];
int main(void) {
std::ios::sync_with_stdio(false);
std::cin >> n >> m >> k;
for(int i = 0, x1, y1, x2, y2; i < k; ++i) {
std::cin >> x1 >> y1 >> x2 >> y2;
S[x1][y1] += 1;
S[x2 + 1][y1] -= 1;
S[x1][y2 + 1] -= 1;
S[x2 + 1][y2 + 1] += 1;
U[x1][y1] += 1, U[x1][y2 + 1] -= 1;
D[x2][y1] += 1, D[x2][y2 + 1] -= 1;
L[x1][y1] += 1, L[x2 + 1][y1] -= 1;
R[x1][y2] += 1, R[x2 + 1][y2] -= 1;
}
for(int i = 1; i <= n; ++i) for(int j = 1; j <= m; ++j)
U[i][j] += U[i][j - 1],
D[i][j] += D[i][j - 1],
L[i][j] += L[i - 1][j],
R[i][j] += R[i - 1][j],
S[i][j] += S[i - 1][j] + S[i][j - 1] - S[i - 1][j - 1];
memset(u, -1, sizeof(u));
for(int i = 1; i <= n; ++i) for(int j = 1; j <= m; ++j) {
if(U[i][j]) u[i][j] = i;
else u[i][j] = u[i - 1][j];
}
memset(d, -1, sizeof(d));
for(int i = n; i >= 1; --i) for(int j = 1; j <= m; ++j) {
if(D[i][j]) d[i][j] = i;
else d[i][j] = d[i + 1][j];
}
memset(l, -1, sizeof(l));
for(int j = 1; j <= m; ++j) for(int i = 1; i <= n; ++i) {
if(L[i][j]) l[i][j] = j;
else l[i][j] = l[i][j - 1];
}
memset(r, -1, sizeof(r));
for(int j = m; j >= 1; --j) for(int i = 1; i <= n; ++i) {
if(R[i][j]) r[i][j] = j;
else r[i][j] = r[i][j + 1];
}
int ans = n * m;
for(int i = 1; i <= n; ++i) for(int j = 1; j <= m; ++j) {
if(S[i][j] == 0) continue;
if(u[i][j] < 0) continue;
if(d[i][j] < 0) continue;
if(l[i][j] < 0) continue;
if(r[i][j] < 0) continue;
ans = std::min(ans, (d[i][j] - u[i][j] + 1) * (r[i][j] - l[i][j] + 1));
}
std::cout << ans << char(10);
return 0;
}
Removing Elements
又是个神秘题,弃疗
Rigged Games
很经典的题,首先可以用 two pointers
快速求出从每个位置出发下次会到串中的哪个位置,并且这一局获胜方是谁
根据这个初始条件直接倍增预处理,询问时找到使得大局不结束的最靠后的位置即可
#include<cstdio>
#include<iostream>
#include<utility>
#define RI register int
#define CI const int&
using namespace std;
typedef pair <int,int> pi;
const int N=100005;
struct ifo
{
int x,y;
inline ifo(CI X=0,CI Y=0)
{
x=X; y=Y;
}
friend inline ifo operator + (const ifo& A,const ifo& B)
{
return ifo(A.x+B.x,A.y+B.y);
}
inline int Max(void)
{
return max(x,y);
}
}w[N][20]; int n,a,b,to[N][20]; char s[N];
int main()
{
RI i,j; scanf("%d%d%d%s",&n,&a,&b,s);
int c[2]={0,0}; ++c[s[0]-'0'];
for (i=j=0;i<n;++i)
{
while (max(c[0],c[1])<a) ++c[s[j=(j+1)%n]-'0'];
to[i][0]=(j+1)%n; w[i][0]=c[0]==a?ifo(0,1):ifo(1,0);
--c[s[i]-'0'];
}
//for (i=0;i<n;++i) printf("i=%d, to=%d, win=(%d,%d)\n",i,to[i][0],w[i][0].x,w[i][0].y);
for (j=1;j<20;++j) for (i=0;i<n;++i)
{
to[i][j]=to[to[i][j-1]][j-1];
w[i][j]=w[i][j-1]+w[to[i][j-1]][j-1];
}
for (i=0;i<n;++i)
{
int pos=i; ifo cur;
for (j=19;j>=0;--j)
if ((cur+w[pos][j]).Max()<b)
cur=cur+w[pos][j],pos=to[pos][j];
cur=cur+w[pos][0];
putchar(cur.x==b?'1':'0');
}
return 0;
}
Slay the Spire: Game Design
唉,塔批;唉,网络流
要求每条路径上都有至少一个怪的情况就是经典的删点使得图不连通,跑个最小割即可
这题要求有 \(k\) 个怪就是把每个点复制 \(k\) 层再跑类似的过程,具体建图方式可以看官方题解
#include<cstdio>
#include<iostream>
#include<cstring>
#include<queue>
#define RI register int
#define CI const int&
using namespace std;
typedef long long LL;
const int N=1005,INF=1e9;
int n,m,k,x,y,in[N],out[N]; vector <int> v[N];
inline int ID(CI d,CI v,CI tp)
{
return (d-1)*(2*n)+tp*n+v;
}
namespace Network_Flow
{
const int NN=10005,MM=NN*20;
struct edge
{
int to,nxt,v;
}e[MM<<1]; int cnt=1,head[NN],cur[NN],dep[NN],clk[NN],s,t; queue <int> q;
inline void addedge(CI x,CI y,CI z)
{
e[++cnt]=(edge){y,head[x],z}; head[x]=cnt;
e[++cnt]=(edge){x,head[y],0}; head[y]=cnt;
}
#define to e[i].to
inline bool BFS(void)
{
memset(dep,0,(t+1)*sizeof(int)); dep[s]=1; q=queue <int>(); q.push(s);
while (!q.empty())
{
int now=q.front(); q.pop();
for (RI i=head[now];i;i=e[i].nxt)
if (e[i].v&&!dep[to]) dep[to]=dep[now]+1,q.push(to);
}
return dep[t];
}
inline int DFS(CI now,CI tar,int dis)
{
if (now==tar) return dis; int ret=0;
for (RI& i=cur[now];i&&dis;i=e[i].nxt)
if (e[i].v&&dep[to]==dep[now]+1)
{
int dist=DFS(to,tar,min(dis,e[i].v));
if (!dist) dep[to]=INF;
dis-=dist; ret+=dist; e[i].v-=dist; e[i^1].v+=dist;
if (!dis) return ret;
}
if (!ret) dep[now]=0; return ret;
}
inline void DFS1(CI now)
{
clk[now]=1; for (RI i=head[now];i;i=e[i].nxt) if (e[i].v&&!clk[to]) DFS1(to);
}
inline void DFS2(CI now)
{
clk[now]=2; for (RI i=head[now];i;i=e[i].nxt) if (e[i].v&&!clk[to]) DFS2(to);
}
#undef to
inline LL Dinic(LL ret=0)
{
while (BFS()) memcpy(cur,head,(t+1)*sizeof(int)),ret+=DFS(s,t,INF); return ret;
}
};
using namespace Network_Flow;
int main()
{
RI i,j; scanf("%d%d%d",&n,&m,&k);
for (i=1;i<=m;++i)
{
scanf("%d%d",&x,&y);
++out[x]; ++in[y]; v[x].push_back(y);
}
for (i=1;i<=n;++i)
{
for (auto to:v[i])
{
if (!in[i]&&!out[to]) return puts("-1"),0;
if (!in[i]) addedge(i,ID(1,to,0),INF); else
if (!out[to]) addedge(ID(k,i,1),to+n,INF); else
{
for (j=1;j<=k;++j) addedge(ID(j,i,1),ID(j,to,0),INF);
}
}
}
s=ID(k,n,1)+1; t=s+1;
for (i=1;i<=n;++i)
{
if (!in[i]) addedge(s,i,INF);
if (!out[i]) addedge(i+n,t,INF);
if (in[i]&&out[i])
{
for (j=1;j<=k;++j) addedge(ID(j,i,0),ID(j,i,1),1);
for (j=1;j+1<=k;++j) addedge(ID(j,i,0),ID(j+1,i,1),INF);
}
}
LL res=Dinic();
if (res>=INF) return puts("-1"),0;
printf("%lld\n",res); DFS1(s); DFS2(t);
for (i=1;i<=n;++i) if (in[i]&&out[i])
{
bool flag=0;
for (j=1;j<=k;++j)
{
if (clk[ID(j,i,0)]==1&&clk[ID(j,i,1)]==2) { flag=1; break; }
}
if (flag) printf("%d ",i);
}
return 0;
}
Sudoku and Minesweeper
签到题,我题意都不知道
#include<bits/stdc++.h>
int main() {
std::string s[9];
int x, y;
for(int i = 0; i < 9; ++i) std::cin >> s[i];
for(int i = 3; i < 6; ++i) for(int j = 3; j < 6; ++j) if(s[i][j] == '8') {
x = i, y = j;
}
for(int i = 0; i < 9; ++i) {
for(int j = 0; j < 9; ++j) if(i == x && j == y)
std::cout << '8';
else std::cout << '*';
std::cout << char(10);
}
return 0;
}
Postscript
唉现在每天都菜的发昏,后期经典等死就完了,什么时候能担起逆转大局的角色呢