The 3rd Universal Cup. Stage 23: Hong Kong
Preface
去年的香港站,本来两个礼拜没摸键盘了,但意外打的很顺,最后出了 7 个题感觉都能干进捧杯区了,只能感慨摆烂使人进步啊
B. Defeat the Enemies
考虑 DP,令 \(f_{i,j}\) 表示一共造成了 \(i\) 点伤害,且被护甲稀释的伤害最多为 \(j\) 所需的最小代价,方案数顺便一起维护
由于被护甲稀释的伤害 \(<k\),因此第二维是 \(O(k)\) 的,DP 转移时只需要枚举下一击的伤害量,总复杂度 \(O(mk^2)\)
实现时有部分边界需要特别注意
#include <bits/stdc++.h>
#define int int64_t
constexpr int mod = 998244353;
void chmax(int &a, int b) {
if(b > a) a = b;
}
void chmin(int &a, int b) {
if(b < a) a = b;
}
void add(int &a, int b) {
if((a += b) >= mod) a -= mod;
}
void work() {
int n, m; std::cin >> n >> m;
std::vector<int> a(n), b(n);
for(auto &a: a) std::cin >> a;
for(auto &b: b) std::cin >> b;
int k; std::cin >> k;
std::vector<int> c(k + 1);
for(int i = 1; i <= k; ++i) std::cin >> c[i];
std::vector<std::array<int, 101>> prep(m + 1);
for(int i = 0; i <= m; ++i) for(int j = 0; j <= 100; ++j)
prep[i][j] = 0;
for(int i = 0; i < n; ++i)
for(int j = 1; j <= k; ++j)
chmax(prep[b[i] - 1][j], a[i] + b[i] + j - 1);
for(int j = 1; j < k; ++j)
for(int i = m; i >= 1; --i)
chmax(prep[i - 1][j + 1], prep[i][j]);
int hkr = 0;
for(int i = 0; i < n; ++i) chmax(hkr, a[i] + b[i]);
const int U = 2 * m + 2 * k + 100;
std::vector<std::array<int, 100>>
dp1(U + 1),
dp2(U + 1);
for(int i = 0; i <= U; ++i) for(int j = 0; j < 100; ++j)
dp1[i][j] = 0x3fffffffffffffffLL, dp2[i][j] = 0;
dp1[0][0] = 0;
dp2[0][0] = 1;
int ans = 0x3fffffffffffffffLL, ans_count = 0;
for(int i = 0; i <= U; ++i) for(int j = 0; j < k; ++j) {
if(i >= hkr + j) {
if(dp1[i][j] < ans) ans = dp1[i][j], ans_count = dp2[i][j]; else
if(dp1[i][j] == ans) add(ans_count, dp2[i][j]);
}
for(int x = 1; x <= k && i + x <= U; ++x) {
int ncost = dp1[i][j] + c[x];
int ni = i + x;
int nj = j;
if(i <= m) chmax(nj, prep[i][x] - hkr);
if(ncost < dp1[ni][nj]) {
dp1[ni][nj] = ncost;
dp2[ni][nj] = dp2[i][j];
} else
if(ncost == dp1[ni][nj]) {
add(dp2[ni][nj], dp2[i][j]);
}
}
}
std::cout << ans << ' ' << ans_count << char(10);
return ;
}
int32_t main() {
std::ios::sync_with_stdio(false);
int T; std::cin >> T; while(T--) work();
return 0;
}
C. The Story of Emperor Bie
签到,首先不难发现答案只可能是最大值所在的位置
手玩一下会发现其实从这些位置的任意一个开始推都是等价的,而题目又保证一定有解,因此输出这些位置即可
#include<cstdio>
#include<iostream>
#include<algorithm>
#define RI register int
#define CI const int&
using namespace std;
const int N=500005;
int t,n,a[N];
int main()
{
for (scanf("%d",&t);t;--t)
{
scanf("%d",&n);
for (RI i=1;i<=n;++i) scanf("%d",&a[i]);
int mx=*max_element(a+1,a+n+1);
for (RI i=1;i<=n;++i) if (a[i]==mx) printf("%d ",i);
putchar('\n');
}
return 0;
}
E. Concave Hull
挺难的一个几何题,祁神赛时+赛后调了挺久才过的,我连题目都没看就不做评论了
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N = 2005;
const int MOD = (int)1e9+7;
void inc(int &x, int a) {if ((x+=a)>=MOD) x-=MOD;}
void dec(int &x, int a) {if ((x-=a)<0) x+=MOD;}
struct Pt {
int x, y;
Pt operator-(const Pt &b) const {return Pt{x-b.x, y-b.y};}
Pt operator+(const Pt &b) const {return Pt{x+b.x, y+b.y};}
bool operator<(const Pt &b) const {return x!=b.x ? x<b.x : y<b.y;}
int crs(const Pt &b) const {return x*b.y-y*b.x;}
int quad() const {
if (x>0 && y>=0) {return 1;} if (x<=0 && y>0) {return 2;}
if (x<0 && y<=0) {return 3;} if (x>=0 && y<0) {return 4;}
return 0;
}
};
struct arcPt {
Pt v;
int qd, id;
bool operator< (const arcPt b) const {
if (qd != b.qd) return qd < b.qd;
else return v.crs(b.v) > 0;
}
};
#define ft first
#define sd second
int n, ans, areaConvh;
int stk[N]; int tp=-1;
Pt pt[N];
bool onConvh[N];
vector<arcPt> arc[N];
vector<int> makeConvh(vector<int> &vec) {
sort(vec.begin(), vec.end(), [&](int a, int b) {return pt[a] < pt[b];});
int sz = vec.size();
vector<int> res(sz+5);
int top = -1;
res[++top] = vec[0];
for (int i=1; i<sz; ++i) {
while (top > 0 && (pt[res[top]]-pt[res[top-1]]).crs(pt[vec[i]]-pt[res[top]]) <= 0) --top;
res[++top] = vec[i];
}
int mx = top;
for (int i=sz-2; i>=0; --i) {
while (top > mx && (pt[res[top]]-pt[res[top-1]]).crs(pt[vec[i]]-pt[res[top]]) <= 0) --top;
res[++top] = vec[i];
}
res.erase(res.begin()+top, res.end());
return res;
}
int getConvhArea(vector<int> convh) {
int res = 0;
int sz = convh.size();
for (int i=0; i<sz; ++i) {
res += (pt[convh[i]].crs(pt[convh[(i+1)%sz]])) % MOD;
}
return res;
}
int cross(int p, int a, int b) {return abs((pt[a]-pt[p]).crs(pt[b]-pt[p]))%MOD ;}
// int getPlace(int center, int after, int x, int sel) {
// Pt v = pt[x] - pt[center];
// int qd = v.quad();
// int res1 = lower_bound(arc[center].begin(), arc[center].end(), arcPt{v, qd, x}) - arc[center].begin();
// int res2 = lower_bound(arc[center].begin(), arc[center].end(), arcPt{v, qd+4, x}) - arc[center].begin();
// if (1==sel) return res1 >= after ? res1 : res2;
// return res2 <= after ? res2 : res1;
// }
signed main() {
ios::sync_with_stdio(0); cin.tie(0);
cin >> n;
for (int i=0; i<n; ++i) {
cin >> pt[i].x >> pt[i].y;
}
vector<int> idd(n);
for (int i=0; i<n; ++i) idd[i] = i;
vector<int> convh = makeConvh(idd);
for (int x : convh) onConvh[x] = true;
areaConvh = getConvhArea(convh);
// printf("areaConvh : %lld\n", areaConvh);
for (int i=0; i<n; ++i) {
for (int j=0; j<n; ++j) if (j!=i) {
Pt v = pt[j]-pt[i];
int qd = v.quad();
arc[i].push_back({v, qd, j});
arc[i].push_back({v, qd+4, j});
}
sort(arc[i].begin(), arc[i].end());
}
// for (int i=0; i<n; ++i) {
// printf("arc[%lld]:", i);
// for (auto [v, qd, x] : arc[i]) printf(" %lld", x);
// puts("");
// }
for (int i=0; i<n; ++i) if (!onConvh[i]) {
int L = 0;
while (!onConvh[arc[i][L].id]) ++L;
int bg = arc[i][L].id;
int cnt = 0;
// int rcnt = 0;
// int rarea = 0;
for (int j=L+1; j<arc[i].size(); ++j) {
++cnt;
if (!onConvh[arc[i][j].id]) continue;
// printf("i=%lld j=%lld crs=%lld\n", i, j, cross(i, arc[i][L].id, arc[i][j].id));
// rarea += cross(i, arc[i][L].id, arc[i][j].id);
ans = (ans + (areaConvh - cross(i, arc[i][L].id, arc[i][j].id) + MOD)%MOD * cnt % MOD)%MOD;
// rcnt += cnt;
cnt = 0;
L = j;
if (arc[i][L].id == bg) break;
}
// printf("rcnt = %lld\n", rcnt);
// printf("rarea = %lld\n", rarea);
}
for (int i=0; i<n; ++i) if (!onConvh[i]) {
int L = 0;
while (!onConvh[arc[i][L].id]) ++L;
int bg = arc[i][L].id;
tp = -1; stk[++tp] = arc[i][L].id;
int sum = (pt[i].crs(pt[arc[i][L].id]) %MOD + MOD)%MOD;
// printf("333333\n");
// vector<int> dbg = vector<int>({i, arc[i][L].id});
for (int j=L+1; j<arc[i].size(); ++j) {
if (onConvh[arc[i][j].id]) {
if (bg == arc[i][j].id) break;
L = j;
tp = -1; stk[++tp] = arc[i][L].id;
sum = (pt[i].crs(pt[arc[i][L].id]) %MOD + MOD)%MOD;
// printf("i=%lld L=%lld id[L]=%lld sum=%lld\n", i, L, arc[i][L].id, sum);
// printf("2222\n");
// dbg = vector<int>({i, arc[i][L].id});
continue;
}
// printf("i=%lld, j=%lld sum=%lld\n", i, j, sum);
while (tp>0 && (pt[stk[tp]]-pt[stk[tp-1]]).crs(pt[arc[i][j].id]-pt[stk[tp-1]]) <= 0) {
sum = (sum - (pt[stk[tp-1]].crs(pt[stk[tp]])%MOD) + MOD)%MOD;
// printf("11111\n");
--tp;
}
// dbg.push_back(arc[i][j].id);
// printf("tp=%lld id[j]=%lld sum=%lld\n", stk[tp].ft, arc[i][j].id, sum);
stk[++tp] = arc[i][j].id;
sum = (sum + (pt[stk[tp-1]].crs(pt[stk[tp]])%MOD) + MOD)%MOD;
ans = (ans + (sum + pt[stk[tp]].crs(pt[i])%MOD + MOD)%MOD)%MOD;
// printf("dbg:"); for (int x : dbg) printf(" %lld", x); puts("");
// auto dbgconvh = makeConvh(dbg); printf("dbgconvh:"); for (int x : dbgconvh) printf(" %lld", x); puts("");
// printf("sum=%lld convh=%lld\n", sum, sum + pt[stk[tp].ft].crs(pt[i]));
// printf("i=%lld j=%lld id[j]=%lld L=%lld id[L]=%lld convh=%lld convh_ans=%lld\n", i, j, arc[i][j].id, L, arc[i][L].id, (sum + pt[stk[tp]].crs(pt[i])), getConvhArea(makeConvh(dbg)));
// assert((sum + pt[stk[tp]].crs(pt[i])) == getConvhArea(makeConvh(dbg)));
}
}
// printf("44444\n");
for (int i=0; i<n; ++i) if (!onConvh[i]) {
int L = arc[i].size() - 1;
while (!onConvh[arc[i][L].id]) --L;
int bg = arc[i][L].id;
tp = -1; stk[++tp] = arc[i][L].id;
int sum = (pt[arc[i][L].id].crs(pt[i]) %MOD + MOD)%MOD;
for (int j=L-1; j>=0; --j) {
if (onConvh[arc[i][j].id]) {
if (bg == arc[i][j].id) break;
L = j;
tp = -1; stk[++tp] = arc[i][L].id;
sum = (pt[arc[i][L].id].crs(pt[i])%MOD + MOD)%MOD;
continue;
}
while (tp>0 && (pt[arc[i][j].id]-pt[stk[tp-1]]).crs(pt[stk[tp]]-pt[stk[tp-1]]) <= 0) {
sum = (sum - pt[stk[tp]].crs(pt[stk[tp-1]])%MOD + MOD)%MOD;
--tp;
}
stk[++tp] = arc[i][j].id;
sum = (sum + pt[stk[tp]].crs(pt[stk[tp-1]])%MOD + MOD) % MOD;
ans = (ans + (sum + pt[i].crs(pt[stk[tp]])%MOD + MOD)%MOD ) % MOD;
// printf("i=%lld j=%lld id[j]=%lld L=%lld id[L]=%lld convh=%lld\n", i, j, arc[i][j].id, L, arc[i][L].id, (sum + pt[i].crs(pt[stk[tp]])));
}
}
cout << ans << '\n';
return 0;
}
F. Money Game 2
感觉很显然的一个题,不知道为啥过的人不算很多
首先观察到对于位置 \(i\),其实就等价于在其左边找 \(x\) 个数,右边找 \(y\) 个数,然后按照顺序传递过来,找到满足 \(x+y<n\) 且最优的方案
一个直观的想法是求出 \(L(i,j)\) 表示位置 \(i\) 左边找了 \(j\) 个数带来的贡献,右侧同理,这样复杂度显然有点爆炸
但仔细思考会发现对于 \(L(i,\cdot)\),本质不同的值只有 \(O(\log a_i)\) 种,且值相同的保留 \(j\) 最小的即可
同时 \(L(i+1,\cdot)\) 的值可以由 \(L(i,\cdot)\) 递推而来,而 \(L(1,\cdot)\) 的值只需要暴力二分找到分界点即可,总复杂度 \(O(n\log a_i)\)
#include<cstdio>
#include<iostream>
#include<vector>
#define int long long
#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=500005;
int t,n,a[N]; vector <pi> L[N],R[N];
inline int calcL(CI k)
{
int ret=0; for (RI i=n-k+1;i<=n;++i) ret+=a[i],ret/=2;
return ret;
}
inline int calcR(CI k)
{
int ret=0; for (RI i=k;i>=1;--i) ret+=a[i],ret/=2;
return ret;
}
signed main()
{
for (scanf("%lld",&t);t;--t)
{
scanf("%lld",&n);
for (RI i=1;i<=n;++i) scanf("%lld",&a[i]);
L[1].push_back(pi(0,0)); int tar=calcL(n-1);
while (L[1].back().se!=tar)
{
int l=L[1].back().fi+1,r=n-1,mid,ret=-1;
while (l<=r)
{
int mid=l+r>>1;
if (calcL(mid)!=L[1].back().se) ret=mid,r=mid-1; else l=mid+1;
}
L[1].push_back(pi(ret,calcL(ret)));
}
for (RI i=2;i<=n;++i)
{
L[i].push_back(pi(0,0));
for (auto [num,val]:L[i-1])
{
int nval=(val+a[i-1])/2;
if (num+1<=n-1&&nval!=L[i].back().se)
L[i].push_back(pi(num+1,nval));
}
}
R[n].push_back(pi(0,0)); tar=calcR(n-1);
while (R[n].back().se!=tar)
{
int l=R[n].back().fi+1,r=n-1,mid,ret=-1;
while (l<=r)
{
int mid=l+r>>1;
if (calcR(mid)!=R[n].back().se) ret=mid,r=mid-1; else l=mid+1;
}
R[n].push_back(pi(ret,calcR(ret)));
}
for (RI i=n-1;i>=1;--i)
{
R[i].push_back(pi(0,0));
for (auto [num,val]:R[i+1])
{
int nval=(val+a[i+1])/2;
if (num+1<=n-1&&nval!=R[i].back().se)
R[i].push_back(pi(num+1,nval));
}
}
for (RI i=1;i<=n;++i)
{
int k=R[i].size()-1,ret=0;
for (RI j=0;j<L[i].size();++j)
{
while (k>=0&&L[i][j].fi+R[i][k].fi>n-1) --k;
if (k>=0) ret=max(ret,L[i][j].se+R[i][k].se);
}
printf("%lld%c",ret+a[i]," \n"[i==n]);
}
for (RI i=1;i<=n;++i) L[i].clear(),R[i].clear();
}
return 0;
}
G. Yelkrab
题都没看,被徐神秒了
#include <bits/stdc++.h>
using u64 = uint64_t;
std::vector<int> hkr[1000006];
struct node_t {
int out[26];
int64_t count;
} node[1000006];
void work() {
int n; std::cin >> n;
u64 ans = 0;
size_t sum_of_pig = 0;
std::vector<std::string> pig(n);
for(auto &pig: pig) std::cin >> pig, sum_of_pig += pig.size();
std::vector<u64> ap(sum_of_pig + 1, 0);
int O = 0;
memset(node + O, 0, sizeof(node_t));
int i = 0;
for(auto &&pig: pig) {
int cur = 0;
for(auto c: pig) {
int u = c - 'a';
if(!node[cur].out[u]) {
O += 1;
memset(node + O, 0, sizeof(node_t));
node[cur].out[u] = O;
}
cur = node[cur].out[u];
int cc = ++node[cur].count;
for(auto hkr: hkr[cc]) {
ans ^= ap[hkr];
ap[hkr] += hkr;
ans ^= ap[hkr];
}
}
std::cout << ans << char(++i == n ? 10 : 32);
}
}
int main() {
for(int i = 1; i <= 1000000; ++i) for(int j = i; j <= 1000000; j += i)
hkr[j].emplace_back(i);
int T; std::cin >> T; while(T--) work();
return 0;
}
H. Mah-jong
首先考虑怎么快速判断一个牌的数量向量 \((c_1,c_2,\dots,c_8)\) 是否可以胡牌,其中 \(c_i\) 表示值为 \(i\) 的牌的数量
不难想到只要枚举所有可能的吃的数量,剩下的只要满足所有牌剩余数量为 \(3\) 的倍数即可
而一种吃的数量只有 \(0,1,2\) 种,因此大力枚举的复杂度为 \(3^6\),可以接受
统计时维护前缀的牌的数量向量,但直接用模意义下相减的方法会出现问题,即区间内可能没有足够的牌用来构建吃
因此还需要顺带维护出合法的左端点能到达的最大值,在这个地方统计答案即可
#include<cstdio>
#include<iostream>
#include<vector>
#include<array>
#define RI register int
#define CI const int&
using namespace std;
typedef array <int,8> st;
const int N=100005,S=6561;
int t,pw3[10],n,a[N],bkt[S];
vector <st> chows; vector <int> mdy[N];
inline void DFS(CI p,st& cur)
{
if (p==6) return (void)(chows.push_back(cur));
for (RI i=0;i<3;++i)
{
for (RI j=0;j<3;++j) cur[p+j]+=i;
DFS(p+1,cur);
for (RI j=0;j<3;++j) cur[p+j]-=i;
}
}
int main()
{
pw3[0]=1; for (RI i=1;i<8;++i) pw3[i]=pw3[i-1]*3;
st tmp; for (RI i=0;i<8;++i) tmp[i]=0; DFS(0,tmp);
// printf("size of chows = %d\n",(int)chows.size());
for (scanf("%d",&t);t;--t)
{
scanf("%d",&n); vector <int> rollback;
for (RI i=1;i<=n;++i) scanf("%d",&a[i]),--a[i];
auto trs=[&](const st& tmp)
{
int mask=0;
for (RI i=0;i<8;++i) mask+=tmp[i]%3*pw3[i];
return mask;
};
st pfx; for (RI i=0;i<8;++i) pfx[i]=0;
vector <int> num[10];
for (RI i=1;i<=n;++i)
{
++pfx[a[i]]; num[a[i]].push_back(i);
for (auto cur:chows)
{
bool flag=1;
for (RI j=0;j<8;++j)
if (pfx[j]<cur[j]) { flag=0; break; }
if (!flag) continue;
int pos=i; for (RI j=0;j<8;++j)
if (cur[j]) pos=min(pos,num[j][(int)num[j].size()-cur[j]]);
int mask=0; for (RI j=0;j<8;++j)
mask+=(pfx[j]%3-cur[j]%3+3)%3*pw3[j];
// printf("i = %d, mask = %d\n",i,mask);
mdy[pos-1].push_back(mask);
}
}
for (RI i=0;i<8;++i) pfx[i]=0;
int mask=trs(pfx); ++bkt[mask]; rollback.push_back(mask);
long long ans=0;
for (RI i=1;i<=n;++i)
{
for (auto mask:mdy[i-1]) ans+=bkt[mask];
++pfx[a[i]];
int mask=trs(pfx); ++bkt[mask]; rollback.push_back(mask);
}
printf("%lld\n",ans);
for (auto x:rollback) bkt[x]=0;
for (RI i=0;i<=n;++i) mdy[i].clear();
}
return 0;
}
K. LR String
徐神开场写的,应该是个签
#include <bits/stdc++.h>
void work() {
std::string original; std::cin >> original;
std::vector<std::pair<char, int>> ori;
bool oL = (original.front() == 'L'),
oR = (original.back() == 'R');
original = original.substr(oL, original.size() - oL - oR);
for(int l = 0, r = 0; l < original.size(); l = r) {
while(r < original.size() && original[l] == original[r])
r++;
ori.emplace_back(original[l], r - l);
}
int T; std::cin >> T; while(T--) {
std::string s; std::cin >> s;
if(oL && s.front() != 'L') {
std::cout << "NO\n";
continue;
}
if(oR && s.back() != 'R') {
std::cout << "NO\n";
continue;
}
s = s.substr(oL, s.size() - oL - oR);
int i = 0, p1 = 0, p2 = 0;
while(i < s.size() && p1 < ori.size()) {
if(p2 == ori[p1].second || s[i] != ori[p1].first) {
p1++; p2 = 0;
} else {
i++; p2++;
}
}
std::cout << (i == s.size() ? "YES\n" : "NO\n");
}
return ;
}
int main() {
std::ios::sync_with_stdio(false);
int T; std::cin >> T; while(T--) work();
return 0;
}
L. Flipping Paths
首先枚举最终的目标颜色,考虑使用以下贪心策略
对于一条路径,从第一行出发,每次尽量延申到这一行最右边的不对应颜色再向下拐,直到到达最后一行时再一直向右
不难发现这种方法一次会清除一条对角线上的所有不合法颜色,由于对角线数量为 \(n+m-1\),因此可以通过
#include<cstdio>
#include<iostream>
#include<string>
#include<vector>
#define RI register int
#define CI const int&
using namespace std;
const int N=205;
int t,n,m,a[N][N]; char s[N][N];
inline bool solve(const char& tar)
{
for (RI i=1;i<=n;++i) for (RI j=1;j<=m;++j)
a[i][j]=(s[i][j]==tar?0:1);
vector <string> res;
while ((int)res.size()<=400)
{
bool all_zero=1;
for (RI i=1;i<=n;++i) for (RI j=1;j<=m;++j)
if (a[i][j]) { all_zero=0; break; }
if (all_zero) break;
string way=""; int y=1;
for (RI x=1;x<=n;++x)
{
int rgt=-1;
for (RI j=m;j>=1;--j)
if (a[x][j]) { rgt=j; break; }
if (x==n) rgt=m;
while (y<rgt)
{
a[x][y]^=1;
way.push_back('R'); ++y;
}
a[x][y]^=1;
if (x!=n) way.push_back('D');
}
res.push_back(way);
}
if ((int)res.size()<=400)
{
puts("YES");
printf("%d\n",(int)res.size());
for (auto way:res) cout<<way<<endl;
return true;
}
return false;
}
int main()
{
for (scanf("%d",&t);t;--t)
{
scanf("%d%d",&n,&m);
for (RI i=1;i<=n;++i) scanf("%s",s[i]+1);
if (solve('W')) continue;
if (solve('B')) continue;
puts("NO");
}
return 0;
}
Postscript
由于要准备组会的汇报,剩下看着可做的 J 就懒得补了,开摆就是爽啊