Preface
久違的多校,又被徐神帶飛力
這場總體可做題挺多的,前期出題也還算穩,但中間祁神寫 H 睿智錯誤頻發直接紅溫爆交了 7 發
但無所謂徐神會出手,上機把當時只過了兩個隊的 G 秒了,然後我爬上去把 B,C 寫了然後對著 D 罰坐一整場
賽後經典看不懂出題人的一句話題解,坐等影片講解吧(雖然看了也不一定看得懂)
Image Scaling
簽到,將邊長除以 \(\gcd\) 即可
#include<cstdio>
#include<iostream>
#include<algorithm>
#define RI register int
#define CI const int&
using namespace std;
const int N=505;
int n,m,W,H; char s[N][N];
int main()
{
scanf("%d%d",&n,&m);
for (RI i=1;i<=n;++i) scanf("%s",s[i]+1);
for (RI i=1;i<=n;++i) for (RI j=1;j<=m;++j)
if (s[i][j]=='x')
{
W=0; while (i+W<=n&&s[i+W][j]=='x') ++W;
H=0; while (j+H<=m&&s[i][j+H]=='x') ++H;
int g=__gcd(W,H); W/=g; H/=g;
for (RI i=1;i<=W;++i,putchar('\n'))
for (RI j=1;j<=H;++j) putchar('x');
return 0;
}
return 0;
}
Break Sequence
和 2024牛客暑期多校訓練營7的 D 思路幾乎一模一樣
樸素的想法就是令 \(f_i\) 表示以 \(i\) 為結尾的劃分方案數,大力 \(O(n^2)\) 轉移
但實際上由於每個數都要滿足個數的限制,因此只看一種數,在固定右端點的情況下它合法的左端點一定是 \(O(m)\) 級別的若干區間
因此類似地用線段樹維護貢獻,和那題的區別就是維護資訊的第二維不再是個數而是對應位置的 DP 方案數之和,還是在最大值等於數的總數時更新答案
當右端點變化時貢獻的討論比較繁瑣,需要耐心且細緻的討論
總複雜度 \(O(nm\log n)\)
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<vector>
#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,INF=1e9,mod=998244353;
int n,m,cnt,vis[N],a[N],b[N],f[N]; vector <int> pos[N];
inline int sum(CI x,CI y)
{
return x+y>=mod?x+y-mod:x+y;
}
inline pi operator + (const pi& A,const pi& B)
{
if (A.fi>B.fi) return A;
if (A.fi<B.fi) return B;
return {A.fi,sum(A.se,B.se)};
}
class Segment_Tree
{
private:
pi O[N<<2]; int tag[N<<2];
inline void pushup(CI now)
{
O[now]=O[now<<1]+O[now<<1|1];
}
inline void apply(CI now,CI mv)
{
O[now].fi+=mv; tag[now]+=mv;
}
inline void pushdown(CI now)
{
if (tag[now]) apply(now<<1,tag[now]),apply(now<<1|1,tag[now]),tag[now]=0;
}
public:
#define TN CI now=1,CI l=1,CI r=n
#define LS now<<1,l,mid
#define RS now<<1|1,mid+1,r
inline void modify(CI beg,CI end,CI mv,TN)
{
if (beg<=l&&r<=end) return apply(now,mv); int mid=l+r>>1; pushdown(now);
if (beg<=mid) modify(beg,end,mv,LS); if (end>mid) modify(beg,end,mv,RS);
pushup(now);
}
inline void update(CI pos,CI mv,TN)
{
if (l==r) return (void)(O[now].se=mv); int mid=l+r>>1; pushdown(now);
if (pos<=mid) update(pos,mv,LS); else update(pos,mv,RS); pushup(now);
}
inline pi query(CI beg,CI end,TN)
{
if (beg<=l&&r<=end) return O[now]; int mid=l+r>>1; pushdown(now);
if (end<=mid) return query(beg,end,LS);
if (beg>mid) return query(beg,end,RS);
return query(beg,end,LS)+query(beg,end,RS);
}
#undef TN
#undef LS
#undef RS
}SEG;
int main()
{
scanf("%d%d",&n,&m);
for (RI i=1;i<=n;++i) scanf("%d",&a[i]);
for (RI i=1;i<=m;++i) scanf("%d",&b[i]);
sort(b+1,b+m+1); f[0]=1;
for (RI i=1;i<=n;++i) if (!vis[a[i]]) ++cnt,vis[a[i]]=1;
for (RI i=1;i<=n;++i)
{
auto work=[&](vector <int>& pos,CI mv)
{
if (m==0) return;
int sz=pos.size(),L,R;
for (RI j=2;j<=m;++j)
{
R=b[j-1]+1; L=b[j];
if (R>sz) R=-INF; else R=pos[sz-R];
if (L>sz) L=1; else L=pos[sz-L]+1;
if (L<=R) SEG.modify(L,R,mv);
}
L=b[1]; R=i;
if (L>sz) L=1; else L=pos[sz-L]+1;
if (L<=R) SEG.modify(L,R,mv);
L=1; R=b[m]+1;
if (R>sz) R=-INF; else R=pos[sz-R];
if (L<=R) SEG.modify(L,R,mv);
};
SEG.update(i,f[i-1]);
SEG.modify(i,i,cnt);
work(pos[a[i]],-1);
pos[a[i]].push_back(i);
work(pos[a[i]],1);
pi tmp=SEG.query(1,i);
//printf("i = %d; cnt = %d; val = %d\n",i,tmp.fi,tmp.se);
if (tmp.fi==cnt) f[i]=tmp.se; else f[i]=0;
}
return printf("%d",f[n]),0;
}
Change Matrix
很典的約數容斥題,比賽的時候貌似寫醜了寫了 \(O(n\log^2 n)\) 的東西上去但還是一發過了
考慮快速計算編號為 \(k\) 的行的貢獻,不難發現這一行的所有數初始時都是 \(k\) 的約數,有:
其中 \(f(g)\) 表示 \([1,n]\) 中與 \(k\) 的 \(\gcd\) 值為 \(g\) 的數的個數,這個很容易用約數容斥算出;\(R(k)\) 為第 \(k\) 行乘上的數
但現在的問題是隨著某些列被乘上一些數後,就不能簡單地用個數來刻畫貢獻了
不過我們可以修改 \(f(g)\) 的定義,將列的乘法貢獻也包含在內,只要在修改列的時候將其約數對應的 \(f(\star)\) 一併修改即可
單次複雜度為 \(d^2(x)\),其中 \(d(x)\) 為 \(x\) 的約數個數,在資料隨機的情況下可以看作 \(O(n\log^2 n)\)
#include<cstdio>
#include<iostream>
#include<vector>
#define RI register int
#define CI const int&
using namespace std;
const int N=100005,mod=1e9+7;
int n,q,R[N],C[N],FR[N],FC[N],ans; vector <int> frac[N];
inline void inc(int& x,CI y)
{
if ((x+=y)>=mod) x-=mod;
}
inline void dec(int& x,CI y)
{
if ((x-=y)<0) x+=mod;
}
inline void init(CI n)
{
for (RI i=1;i<=n;++i) for (RI j=i;j<=n;j+=i) frac[j].push_back(i);
}
inline int calcR(CI k)
{
int ret=0; vector <int> tmp(frac[k].size(),0);
for (RI i=frac[k].size()-1;i>=0;--i)
{
int d=frac[k][i]; tmp[i]=FC[d];
for (RI j=i+1;j<frac[k].size();++j)
if (frac[k][j]%d==0) dec(tmp[i],tmp[j]);
inc(ret,1LL*d*tmp[i]%mod);
}
return 1LL*ret*R[k]%mod;
}
inline int calcC(CI k)
{
int ret=0; vector <int> tmp(frac[k].size(),0);
for (RI i=frac[k].size()-1;i>=0;--i)
{
int d=frac[k][i]; tmp[i]=FR[d];
for (RI j=i+1;j<frac[k].size();++j)
if (frac[k][j]%d==0) dec(tmp[i],tmp[j]);
inc(ret,1LL*d*tmp[i]%mod);
}
return 1LL*ret*C[k]%mod;
}
int main()
{
scanf("%d%d",&n,&q); init(n);
for (RI i=1;i<=n;++i) R[i]=C[i]=1,FR[i]=FC[i]=n/i;
for (RI i=1;i<=n;++i) inc(ans,calcR(i));
for (RI i=1;i<=q;++i)
{
char opt[5]; int x,y;
scanf("%s%d%d",opt,&x,&y);
if (opt[0]=='R')
{
dec(ans,calcR(x));
for (auto d:frac[x]) dec(FR[d],R[x]);
R[x]=1LL*R[x]*y%mod;
for (auto d:frac[x]) inc(FR[d],R[x]);
inc(ans,calcR(x));
} else
{
dec(ans,calcC(x));
for (auto d:frac[x]) dec(FC[d],C[x]);
C[x]=1LL*C[x]*y%mod;
for (auto d:frac[x]) inc(FC[d],C[x]);
inc(ans,calcC(x));
}
printf("%d\n",ans);
}
return 0;
}
Luner XOR
看起來是個可做題,比賽的時候徐神和祁神在討論一個 sosdp 的做法,但沒有 Rush 出來
賽後看題解就只有一句話,式子中間也沒有轉化啥的直接歇逼,只能等講題影片出來去試著聽一手了
Sticky Pistons
徐神偉大,無需多言
據徐神說好像就是個經典的貪心,但我題目都沒看就不作評論了
#include <bits/stdc++.h>
int a[1005] = {-100};
std::vector<int> push(int n, int k) {
int p = n + 1, q = p - 1;
std::vector<int> res;
std::vector<std::pair<int, int>> op;
while(p > 0) {
while(p - q <= k && a[p] - p == a[q] - q) q--;
if(q == p - 1) { p = q; continue; }
res.push_back(q + 1);
op.push_back({q + 2, p});
if(p - q > k && a[p] - p == a[q] - q) break;
p = q;
}
for(auto [l, r]: op) {
// std::cout << "[" << l << ", " << r << "]\n";
for(int i = l; i <= r; ++i) a[i]++;
}
// if(res.size()) {
// std::cout << "debug: ";
// for(int i = 1; i <= n + 1; ++i) std::cout << a[i] << char(i == n + 1 ? 10 : 32);
// }
return res;
}
void printv(std::vector<std::vector<int>> ops) {
std::cout << ops.size() << char(10);
for(auto op: ops) {
std::sort(op.begin(), op.end());
std::cout << op.size();
for(auto op: op) std::cout << ' ' << op;
std::cout << char(10);
}
}
void work() {
int n, k;
std::cin >> n >> k;
std::vector<std::vector<int>> ans1, ans2;
for(int i = 1; i <= n + 1; ++i) a[i] = i;
for(;;) {
auto v = push(n, k);
if(v.empty()) break;
ans1.push_back(v);
}
for(int i = 1; i <= n + 1; ++i) a[i] = i;
for(;;) {
auto v = push(n, 1);
if(v.empty()) break;
ans2.push_back(v);
}
printv(ans1);
std::reverse(ans2.begin(), ans2.end());
printv(ans2);
}
int main() {
std::ios::sync_with_stdio(false);
int t; std::cin >> t; while(t--) work();
return 0;
}
Two Convex Polygons
簡單手玩發現答案就是 \(A\) 的周長加上以 \(B\) 的直徑為半徑的圓的周長
求凸包直徑可以經典旋轉卡殼,然後這題就做完了
#include<bits/stdc++.h>
using namespace std;
#define int long long
using LD = long double;
constexpr LD PI = acos(-1.0L);
struct Pt{
int x, y;
int len2()const{return x*x+y*y;}
LD len()const{return sqrtl(x*x+y*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};}
};
int cross(const Pt &p, const Pt &a, const Pt &b){return abs((a-p).crs(b-p));}
void solve(){
int n; cin >> n; vector<Pt> A(n);
for (int i=0; i<n; ++i){
int x, y; cin >> x >> y;
A[i] = Pt{x, y};
}
int m; cin >> m; vector<Pt> B(m*2+1);
for (int i=0; i<m; ++i){
int x, y; cin >> x >> y;
B[m+i] = B[i] = Pt{x, y};
}
B[2*m] = B[0];
int R2 = 0;
for (int i=0, j=0; i<m; ++i){
R2 = max({R2, (B[j]-B[i]).len2(), (B[j]-B[i+1]).len2()});
while (j<2*m && cross(B[i], B[i+1], B[j]) <= cross(B[i], B[i+1], B[j+1])){
++j;
R2 = max({R2, (B[j]-B[i]).len2(), (B[j]-B[i+1]).len2()});
}
}
LD ans=0.0L;
for (int i=0; i<n; ++i){
ans += (A[(i+1)%n]-A[i]).len();
}
ans += 2*PI*sqrtl(R2);
cout << ans << '\n';
}
signed main(){
ios::sync_with_stdio(0); cin.tie(0);
cout << setiosflags(ios::fixed) << setprecision(15);
int t; cin >> t; while (t--) solve();
return 0;
}
Interesting Numbers
不難發現把 \(L,R\) 分成兩部分後兩邊的取值都只有 \(10^{30}\) 範圍,在 __int128
可表示的範圍內
很容易根據前半部分的取值是否卡住邊界來推出後半部分的取值範圍,計算數量的話只要開根號即可
但為了避免精度誤差建議手動二分開根,不然可能會爆精度
#include<cstdio>
#include<iostream>
#define RI register int
#define CI const int&
using namespace std;
typedef __int128 i128;
const int N=65;
int n; i128 pw[35]; char L[N],R[N];
inline void write(i128 x)
{
if (x>9) write(x/10); putchar(x%10+'0');
}
inline i128 calc(i128 L,i128 R)
{
if (L>R) return 0;
i128 l=0,r=pw[15],retl=-1;
while (l<=r)
{
i128 mid=l+(r-l)/2;
if (mid*mid>=L) retl=mid,r=mid-1;
else l=mid+1;
}
if (retl==-1) return 0;
l=0; r=pw[15]; i128 retr=-1;
while (l<=r)
{
i128 mid=l+(r-l)/2;
if (mid*mid<=R) retr=mid,l=mid+1;
else r=mid-1;
}
if (retr==-1) return 0;
if (retl>retr) return 0;
return retr-retl+1;
}
int main()
{
scanf("%d%s%s",&n,L+1,R+1);
pw[0]=1; for (RI i=1;i<=30;++i) pw[i]=pw[i-1]*10;
i128 L1=0,L2=0,R1=0,R2=0;
for (RI i=1;i<=n/2;++i) L1=L1*10+L[i]-'0';
for (RI i=n/2+1;i<=n;++i) L2=L2*10+L[i]-'0';
for (RI i=1;i<=n/2;++i) R1=R1*10+R[i]-'0';
for (RI i=n/2+1;i<=n;++i) R2=R2*10+R[i]-'0';
i128 ans=calc(L1+1,R1-1)*calc(0,pw[n/2]-1);
if (L1!=R1)
{
ans+=calc(L1,L1)*calc(L2,pw[n/2]-1);
ans+=calc(R1,R1)*calc(0,R2);
} else ans+=calc(L1,R1)*calc(L2,R2);
return write(ans),0;
}
Kill The Monsters
注意到如果對一個怪用了多種操作,則優先進行操作二一定最優
特判掉 \(k=1\) 的情況後,考慮列舉進行的操作二的次數,不難發現每次操作一定是對當前最大的數執行
用堆動態維護全域性最大值,由於操作二的次數不超過 \(O(n\log a_i)\),因此總複雜度 \(O(n\log a_i\log n)\) 可以透過
#include<cstdio>
#include<iostream>
#include<queue>
#define int long long
#define RI register int
#define CI const int&
using namespace std;
const int N=100005;
int n,k,a[N];
signed main()
{
scanf("%lld%lld",&n,&k); int mx=0;
for (RI i=1;i<=n;++i) scanf("%lld",&a[i]),mx=max(mx,a[i]);
if (k==1) return printf("%lld\n",mx),0;
priority_queue <int> hp; int ans=mx;
for (RI i=1;i<=n;++i) hp.push(a[i]);
for (int c=1;;++c)
{
int tmp=hp.top(); hp.pop();
if (tmp==0) break;
tmp/=k; hp.push(tmp);
ans=min(ans,hp.top()+c);
}
return printf("%lld",ans),0;
}
Postscript
今天這場結束後好像只有週四週五兩場多校了(週日的 HDU 要去打 Astar 所以不打),這麼看來暑假也是一轉眼要結束了