Preface
今天由於是我們隊搬毒瘤場,因此下午就不用集中訓練索性繼續 VP UCup
這場題很有外國場的風格,程式碼量和科技含量都不大,只要動動腦筋就行了,最後也是剛好打到了 10 題下班
A. Aliases
不難發現假設 \(a=b=0\),則 \(c\le \log_{10} n\le 7\),因此只要考慮 \(a+b+c\le 7\) 的情況,直接列舉即可
#include<cstdio>
#include<iostream>
#include<string>
#include<map>
#define RI register int
#define CI const int&
using namespace std;
const int N=200005;
int t,n,pw[10]; string A[N],B[N];
int main()
{
ios::sync_with_stdio(0); cin.tie(0);
RI i; for (pw[0]=i=1;i<=7;++i) pw[i]=pw[i-1]*10;
for (cin>>t;t;--t)
{
for (cin>>n,i=1;i<=n;++i) cin>>A[i]>>B[i];
for (RI sum=1;sum<=7;++sum)
{
for (RI a=0;a<=sum;++a)
for (RI b=0;a+b<=sum;++b)
{
int c=sum-a-b,mx=0; map <string,int> rst;
for (i=1;i<=n;++i)
{
string tmp=A[i].substr(0,a)+B[i].substr(0,b);
mx=max(mx,++rst[tmp]);
if (mx>pw[c]) break;
}
if (mx<=pw[c])
{
cout<<a<<' '<<b<<' '<<c<<'\n';
goto exit;
}
}
}
exit:;
}
return 0;
}
B. Bars
這題在去年的 2023年電子科技大學ACM-ICPC暑假前集訓-數學 裡做過,因此直接貼當時寫的題解了
首先考慮樸素的DP做法,不難想到設\(f_i\)表示\(i\)位置為關鍵點,\([1,i]\)這些位置能得到的最大價值
轉移的時候就是列舉上一個關鍵點\(j\),此時\([j,i-1]\)中這些位置的右邊第一個關鍵點都是\(a_i\),且\([j+1,i]\)這些位置的左邊第一個關鍵點都是\(a_j\)
因此不難寫出轉移方程:
這種DP方程第一眼考慮決策單調性或者斜率最佳化,但實操一下會發現沒法處理,因為既有\(j\times a_i\)的項,也有\(i\times a_j\)的項
看似走投無路了,但根據不那麼顯然的觀察可以看出,\((i-j)\times (a_i+a_j)\)是以\((i,0).(i,a_i),(j,0),(j,a_j)\)四點構成的梯形面積的兩倍
因此我們可以把原題意轉化為,給定平面上的若干點\((i,a_i)\),求它們與\(x\)軸形成的最大面積的多邊形
那麼很顯然就是求這些點構成的凸包即可,直接單調棧搞一波,複雜度\(O(n)\)
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N = 5e5+5;
struct Pt{
int x, y;
int crs(const Pt &b)const{return x*b.y-y*b.x;}
Pt operator-(const Pt &b)const{return Pt{x-b.x, y-b.y};}
}pt[N];
int cross(Pt p, Pt a, Pt b){return (a-p).crs(b-p);}
int t, n, A[N];
Pt stk[N]; int tp=0;
signed main(){
ios::sync_with_stdio(0); cin.tie(0);
cin >> t;
while (t--){
cin >> n;
for (int i=1; i<=n; ++i) cin >> A[i], pt[i]=Pt{i, A[i]};
tp=-1;
stk[++tp] = pt[1];
stk[++tp] = pt[2];
for (int i=3; i<=n; ++i){
while (tp>0 && cross(stk[tp-1], stk[tp], pt[i])>=0) --tp;
stk[++tp] = pt[i];
}
// for (int i=0; i<=tp; ++i) printf("(%lld %lld)\n", stk[i].x, stk[i].y);
int ans=0;
for (int i=1; i<=tp; ++i){
ans += (stk[i-1].y+stk[i].y)*(stk[i].x-stk[i-1].x);
}
cout << ans << '\n';
}
return 0;
}
C. Ctrl+C Ctrl+V
簽到,令 \(f_{i,mask}\) 表示處理了前 \(i\) 位,從第 \(i\) 位往前的 \(mask\) 狀態是否被修改,轉移十分顯然
#include<cstdio>
#include<iostream>
#include<cstring>
#define RI register int
#define CI const int&
using namespace std;
const int N=1e6+5,INF=1e9;
int t,f[N][1<<4]; char s[N];
int main()
{
for (scanf("%d",&t);t;--t)
{
RI i,j,k; scanf("%s",s+1); int n=strlen(s+1);
if (n<=3) { puts("0"); continue; }
for (i=1;i<=n;++i) for (j=0;j<16;++j) f[i][j]=INF;
for (j=0;j<16;++j) f[4][j]=__builtin_popcount(j);
if (s[1]=='a'&&s[2]=='n'&&s[3]=='i'&&s[4]=='a') f[4][0]=INF;
for (i=4;i<n;++i) for (j=0;j<16;++j) if (f[i][j]!=INF)
{
for (k=0;k<2;++k)
{
int mask=((j<<1)&15)|k;
if (mask==0&&s[i-2]=='a'&&s[i-1]=='n'&&s[i]=='i'&&s[i+1]=='a') continue;
f[i+1][mask]=min(f[i+1][mask],f[i][j]+k);
}
}
int ans=INF; for (j=0;j<16;++j) ans=min(ans,f[n][j]);
printf("%d\n",ans);
}
return 0;
}
D. Dazzling Mountain
簽到,對於每個點 \(x\),求出其子樹大小 \(sz_x\),然後用一個桶判斷下每種 \(sz\) 對應的葉子數量是否等於總數即可
#include<cstdio>
#include<iostream>
#include<vector>
#define RI register int
#define CI const int&
using namespace std;
const int N=1e6+5;
int t,n,x,y,leaf,l[N],sz[N],bkt[N]; vector <int> v[N];
inline void DFS(CI now=1,CI fa=0)
{
l[now]=0; sz[now]=1;
for (auto to:v[now]) if (to!=fa)
DFS(to,now),l[now]+=l[to],sz[now]+=sz[to];
if (sz[now]==1) ++l[now],++leaf;
bkt[sz[now]]+=l[now];
}
int main()
{
for (scanf("%d",&t);t;--t)
{
RI i; for (scanf("%d",&n),i=1;i<=n;++i) v[i].clear(),bkt[i]=0;
for (i=1;i<n;++i) scanf("%d%d",&x,&y),v[x].push_back(y),v[y].push_back(x);
vector <int> ans; leaf=0;
for (DFS(),i=1;i<=n;++i) if (bkt[i]==leaf) ans.push_back(i);
//for (i=1;i<=n;++i) printf("%d %d\n",sz[now],l[now]);
printf("%d\n",ans.size());
for (auto x:ans) printf("%d ",x); putchar('\n');
}
return 0;
}
E. Euclidean Algorithm
想到關鍵就很簡單的一個題
首先要注意到 \(2a-b\) 的變化可以看作在數軸上有若干個點,每次可以把某個點 \(b\) 作關於另一個點 \(a\) 的對稱點
不難發現無論怎麼操作任意兩點之間的距離都不會縮小,最小值就是初始時的 \(b-a\)
因此有解的充要條件就是 \(\gcd(a,b)=a\bmod (b-a)\),統計合法的方案數也非常套路
不妨先欽定 \(\gcd(a,b)=1\) 對應的答案為 \(f(x)\),則最後答案為 \(\sum_{i=1}^n f(\lfloor\frac{n}{i}\rfloor)\)
滿足 \(1=a\bmod (b-a)\) 的對數也很好計算,列舉 \(b-a\) 的值 \(j\),則畫畫圖會發現只有 \(\lfloor\frac{x-1}{j}\rfloor\) 種方案,則 \(f(x)=\sum_{j=1}^{x-1} \lfloor\frac{x-1}{j}\rfloor\)
用除法分塊套除法分塊,時間複雜度 \(O(n^{\frac{3}{4}})\),由於時限很大可以透過(跑了 24531ms)
#include<cstdio>
#include<iostream>
#define int long long
#define RI register int
#define CI const int&
using namespace std;
int t,n;
inline void write(const __int128& x)
{
if (x>=10) write(x/10); putchar(x%10+'0');
}
signed main()
{
for (scanf("%lld",&t);t;--t)
{
scanf("%lld",&n); __int128 ans=0;
auto f=[&](int n)
{
__int128 ret=0; --n;
for (int l=1,r;l<=n;l=r+1)
r=n/(n/l),ret+=__int128(r-l+1)*(n/l);
return ret;
};
for (int l=1,r;l<=n;l=r+1)
r=n/(n/l),ans+=__int128(r-l+1)*f(n/l);
write(ans); putchar('\n');
}
return 0;
}
F. Flower Garden
感覺不太可做,棄療
G. Great Chase
這題祁神看了眼就秒了,我題意都不知道
#include<bits/stdc++.h>
using namespace std;
#define int long long
using LD = long double;
const LD eps = 1e-8;
int sgn(LD x){return fabs(x)<=eps ? 0 : (x>eps ? 1 : -1);}
const int N = 4e5+5;
int t, n, p[N], v[N];
const LD INF = 1e18;
bool check(LD x){
LD mx=-INF, mn=INF;
for (int i=1; i<=n; ++i){
if (p[i]>0) mn=min(mn, p[i]-v[i]*x);
else mx=max(mx, p[i]+v[i]*x);
}
return sgn(mx-mn)<0;
}
signed main(){
ios::sync_with_stdio(0); cin.tie(0);
cout << setiosflags(ios::fixed) << setprecision(10);
cin >> t;
while (t--){
cin >> n >> v[0];
for (int i=1; i<=n; ++i) cin >> p[i] >> v[i];
LD L=0.0L, R=1e13;
for (int tt=0; tt<200; ++tt){
// printf("L=%Lf R=%Lf\n", L, R);
LD M = (R+L)*0.5L;
if (check(M)) L=M;
else R=M;
}
cout << L*v[0] << '\n';
}
return 0;
}
H. Hyperloop
看起來也是個神仙題,棄療
I. Investors
沒有證明,全是猜測,但就是能過題.jpg
首先直覺告訴我們這個問題等價於把原序列分成 \(k+1\) 段,每段之間不會相互影響,因此總逆序對數量就是所有段逆序對數量之和
大力 DP 複雜度顯然是 \(O(n^3)\) 的,但這種分段類的 DP 一般肯定會有決策單調性,懶得證明了直接爬上去寫完就過了
由於這場沒有題解我也不知道這玩意咋證的,總之能過就行,複雜度 \(O(n^2\log n)\)
#include<cstdio>
#include<iostream>
#include<algorithm>
#define RI register int
#define CI const int&
using namespace std;
const int N=6005,INF=1e9;
int t,n,m,k,a[N],rst[N],g[N][N],f[N][N];
class Tree_Array
{
private:
int bit[N];
public:
#define lowbit(x) (x&-x)
inline int get(RI x,int ret=0)
{
for (;x<=m;x+=lowbit(x)) ret+=bit[x]; return ret;
}
inline void add(RI x,CI y)
{
for (;x;x-=lowbit(x)) bit[x]+=y;
}
#undef lowbit
}BIT;
inline void solve(CI k,CI l=1,CI r=n,CI L=1,CI R=n)
{
if (l>r) return; int mid=l+r>>1,pos;
for (RI i=L;i<=R&&i<=mid;++i)
if (f[k-1][i-1]+g[i][mid]<f[k][mid])
f[k][mid]=f[k-1][i-1]+g[i][mid],pos=i;
solve(k,l,mid-1,L,pos); solve(k,mid+1,r,pos,R);
}
int main()
{
for (scanf("%d",&t);t;--t)
{
RI i,j; for (scanf("%d%d",&n,&k),i=1;i<=n;++i)
scanf("%d",&a[i]),rst[i]=a[i];
sort(rst+1,rst+n+1); m=unique(rst+1,rst+n+1)-rst-1;
for (i=1;i<=n;++i) a[i]=lower_bound(rst+1,rst+m+1,a[i])-rst;
for (i=1;i<=n;++i)
{
g[i][i]=0; BIT.add(a[i],1);
for (j=i+1;j<=n;++j)
g[i][j]=g[i][j-1]+BIT.get(a[j]+1),BIT.add(a[j],1);
for (j=i;j<=n;++j) BIT.add(a[j],-1);
}
for (++k,i=1;i<=n;++i) f[1][i]=g[1][i];
for (i=2;i<=k;++i)
{
for (j=1;j<=n;++j) f[i][j]=INF;
solve(i);
}
printf("%d\n",f[k][n]);
}
return 0;
}
J. Job for a Hobbit
看起來是防 AK 題,棄療
K. Kooky Tic-Tac-Toe
大力模擬題,由於我們隊開場開題順序有問題所以導致罰時極其抽象,因此定下鐵律如果有人開場 1h 內上機寫了 100 行以上的東西就自動下機
具體判斷時可以列舉最後下的一顆棋子,使得拿去這顆子後遊戲不結束,但細節還是比較多的
#include<bits/stdc++.h>
using namespace std;
const int N = 10;
int t, n, k;
struct Node{
string tbl[N];
};
using pii = pair<int, int>;
int calc(Node &cur, int r, int c, int dr, int dc){
int res=1;
for (int x=1; x<=n; ++x){
int nr=r+dr*x, nc=c+dc*x;
if (nr<=0 || nr>n || nc<=0 || nc>n) break;
if (cur.tbl[nr][nc]!=cur.tbl[r][c]) break;
++res;
}
return res;
}
pii findChain(Node &cur){
int anso=0, ansx=0;
for (int i=1; i<=n; ++i){
for (int j=1; j<=n; ++j){
if ('.'==cur.tbl[i][j]) continue;
if ('o'==cur.tbl[i][j]){
anso = max(anso, calc(cur, i, j, 0, 1));
anso = max(anso, calc(cur, i, j, 1, 0));
anso = max(anso, calc(cur, i, j, 1, 1));
anso = max(anso, calc(cur, i, j, 1, -1));
}
if ('x'== cur.tbl[i][j]){
ansx = max(ansx, calc(cur, i, j, 0, 1));
ansx = max(ansx, calc(cur, i, j, 1, 0));
ansx = max(ansx, calc(cur, i, j, 1, 1));
ansx = max(ansx, calc(cur, i, j, 1, -1));
}
}
}
return {anso, ansx};
}
void genMove(vector<pii> &res, Node &cur, char ch){
vector<pii> vec1, vec2;
for (int i=1; i<=n; ++i) for (int j=1; j<=n; ++j){
if ('.'==cur.tbl[i][j]) continue;
if (ch==cur.tbl[i][j]) vec1.emplace_back(i, j);
else vec2.emplace_back(i, j);
}
res.clear();
for (int i=0; i<(int)vec1.size(); ++i){
res.push_back(vec1[i]);
if (i<vec2.size()) res.push_back(vec2[i]);
}
}
void solve(){
}
signed main(){
ios::sync_with_stdio(0); cin.tie(0);
cin >> t;
while (t--){
Node nd;
cin >> n >> k;
// printf("n=%d k=%d\n", n, k);
for (int i=1; i<=n; ++i){
cin >> nd.tbl[i];
nd.tbl[i] = '$' + nd.tbl[i];
}
int cntx=0, cnto=0, cntd=0;
for (int i=1; i<=n; ++i) for (int j=1; j<=n; ++j){
if ('o'==nd.tbl[i][j]) ++cnto;
else if ('x'==nd.tbl[i][j]) ++cntx;
else ++cntd;
}
if (abs(cntx-cnto)>1){
cout << "NIE\n";
continue;
}
auto [anso, ansx] = findChain(nd);
bool ok=true;
if (ansx>=k && anso>=k) ok=false;
if (ansx>=2*k || anso>=2*k) ok=false;
if (!ok) cout << "NIE\n";
else{
char winner='.';
if (ansx>=k) winner='x';
if (anso>=k) winner='o';
if ('.'==winner){
if (0==cntd){
vector<pii> res;
genMove(res, nd, (cntx>cnto ? 'x' : 'o'));
cout << "TAK\n";
for (auto [x, y] : res){
cout << x << ' ' << y << '\n';
}
}else cout << "NIE\n";
}else if (('o'==winner && cnto<cntx) || ('x'==winner && cntx<cnto)){
cout << "NIE\n";
}else{
ok=false;
int ar, ac;
Node nxt;
for (int i=1; i<=n; ++i){
for (int j=1; j<=n; ++j){
if (winner==nd.tbl[i][j]){
nxt=nd;
nxt.tbl[i][j]='.';
auto [lo, lx] = findChain(nxt);
if (lo<k && lx<k){
ar=i, ac=j;
ok=true; break;
}
}
}
if (ok) break;
}
if (!ok) cout << "NIE\n";
else{
vector<pii> res;
char ch;
if (cntx!=cnto){
ch = (cntx>cnto ? 'x' : 'o');
}else{
ch = (winner=='o' ? 'x' : 'o');
}
genMove(res, nxt, ch);
cout << "TAK\n";
for (auto [x, y] : res){
cout << x << ' ' << y << '\n';
}
cout << ar << ' ' << ac << '\n';
}
}
}
}
return 0;
}
L. Line Replacements
很有思維含量的一個題
首先考慮倒著處理,把刪邊變成加邊,再次之前先判斷給出的森林是否合法
顯然對於每個聯通塊,葉子節點必須都是加油站,而中間的非加油站節點的判定就需要思考一下
對於某個非加油站節點,設與其相鄰的所有邊的權值和為 \(sum\),顯然 \(sum\) 必須為偶數
同時令這些邊中邊權最大的為 \(mxval\),若 \(mxval>\frac{sum}{2}\) 則一定無解
再考慮集合中的邊,在加入一條兩段不同時為加油站的邊時,首先要滿足加入的邊邊權為偶數,其次要滿足當前加入的邊的邊權不超過此時這個點相鄰的邊權和
不難發現加邊時的限制和判斷原聯通塊合法的限制是一體兩面的,而加邊的策略也很簡單,就是按照邊權從小到大排序即可
#include <bits/stdc++.h>
std::vector<int> work() {
int n, p;
std::cin >> n >> p;
std::vector<bool> hkr(n, false);
std::vector<int64_t> hbk(n, 0);
std::vector<int> deg(n, 0);
std::vector<std::array<int, 3>> edge(n - 1);
for(int i = 0, s; i < p; ++i) std::cin >> s, hkr[s - 1] = true;
for(int i = 0; i < n - 1; ++i) {
auto &[f, t, val] = edge[i];
std::cin >> f >> t >> val;
deg[--f]++, deg[--t]++;
}
int k; std::cin >> k;
std::vector<int> ars(k);
std::vector<bool> dld(n - 1, false);
for(auto &ars: ars) std::cin >> ars, dld[--ars] = true;
for(int i = 0; i < n - 1; ++i) if(dld[i]) {
auto [f, t, _] = edge[i];
deg[f]--, deg[t]--;
} else {
auto [f, t, v] = edge[i];
hbk[f] += v, hbk[t] += v;
}
for(int i = 0; i < n; ++i) if(!hkr[i] && (deg[i] == 1 || hbk[i] % 2 == 1)) return {};
// for(int i = 0; i < n; ++i) std::cout << hbk[i] << char(i == n - 1 ? 10 : 32);
for(int i = 0; i < n - 1; ++i) if(!dld[i]) {
auto [f, t, v] = edge[i];
if(v + v > hbk[f] && !hkr[f] || v + v > hbk[t] && !hkr[t]) return {};
}
// std::cout << "Blyat\n";
std::sort(ars.begin(), ars.end(), [&](const int &x, const int &y) {
return edge[x][2] < edge[y][2];
});
for(int i = 0; i < ars.size(); ++i) {
auto [f, t, v] = edge[ars[i]];
dld[i] = false;
if(hkr[f] && hkr[t]) continue;
if(v % 2) return {};
for(auto c: (int[2]){f, t}) if(!hkr[c]) {
if(v > hbk[c]) return {};
hbk[c] += v;
}
}
std::reverse(ars.begin(), ars.end());
return ars;
}
int main() {
std::ios::sync_with_stdio(false);
int z; std::cin >> z; while(z--) {
auto ans = work();
if(ans.empty()) std::cout << "NIE\n";
else {
std::cout << "TAK\n";
for(int i = 0; i < ans.size(); ++i) std::cout << ans[i] + 1 << char(i == ans.size() - 1 ? 10 : 32);
}
}
return 0;
}
M. Minor Evil
注意到同樣可以倒著處理,將殺人改為復活,則顯然若從後往前的某天可以復活一個人,則這個人被複活一定是最優的
#include <bits/stdc++.h>
int main() {
std::ios::sync_with_stdio(false);
int z; std::cin >> z; while(z--) {
int n, k; std::cin >> n >> k;
std::vector<bool> alive(n, true);
std::vector<std::pair<int, int>> edge(k);
for(auto &[f, t]: edge) {
std::cin >> f >> t;
f--, t--;
}
int s; std::cin >> s;
for(int i = 0, si; i < s; ++i) {
std::cin >> si;
alive[--si] = false;
}
std::string ans(k, 'N');
for(int i = k - 1; ~i; --i) {
auto [a, b] = edge[i];
if(alive[a] && !alive[b]) {
ans[i] = 'T';
alive[b] = true;
}
}
int i;
for(i = 0; i < n; ++i) if(!alive[i]) {
std::cout << "NIE\n";
break;
}
if(i == n) std::cout << "TAK\n" << ans << char(10);
}
return 0;
}
Postscript
這就是神秘外國場啊,打完一掃前兩天每天幾個題的陰霾,瞬間找回自信