Preface
在寢室白蘭了一週多後也是終於等到徐神歸來開始訓練了
這場的題感覺比較偏數學了,感覺和之前打的一個 Tokyo 的 Open Cup 很像,因此後期挺坐牢的
4h 左右堪堪寫出 7 題,最後全隊 Rush D 結果發現暴力打表都打錯了,怎麼回事呢
A. Square Sum
這題在 去年暑假前集訓數學專題 中做過類似的版本,只不過那個題的模數是沒有平方質因子的奇質數
這題思路應該也是類似,但需要對 \(2\) 的情況進行一些討論,程式碼先坑著有空來補
B. Super Meat Bros
題都沒看,鑑定為不可做
C. Testing Subjects Usually Die
題都沒看,鑑定為不可做
D. Triterminant
感覺思路沒啥問題,就是三步走:解行列式——找合法序列的充要條件——計算最小操作次數
第一步就是線代課後習題的難度,不過推出式子後得到的結論很神秘,然後跑了個暴力打表發現並沒有找到什麼規律
後面看了 WIFI 的程式碼發現似乎是暴力寫掛了(或者是前面搞錯了),需要再反思下
E. Garbage Disposal
分類討論題,首先特判掉 \(L=R\) 的情況
考慮若 \([L,R]\) 中有偶數個數,顯然讓相鄰兩個數兩兩配對一定合法
否則若 \([L,R]\) 中有奇數個數,若此時 \(L\) 為奇數,則可以讓前 \(3\) 個數按:\((L,L+1),(L+1,L+2),(L+2,L)\) 的方式來配對
因為 \((L+2,L)\) 一定互質,而其餘兩組都是相鄰的數因此也必然互質;剩下後面的偶數個數還是類似地兩兩配對即可
否則當 \(L\) 為偶數時一定無解,因為根據鴿籠原理,必然會有至少一對偶數要配對,顯然無解
#include<cstdio>
#include<iostream>
#define RI register int
#define CI const int&
using namespace std;
int t,L,R;
int main()
{
for (scanf("%d",&t);t;--t)
{
scanf("%d%d",&L,&R);
if (L==R)
{
puts(L==1?"1":"-1"); continue;
}
if ((R-L+1)%2==0)
{
for (RI i=L;i+1<=R;i+=2)
printf("%d %d ",i+1,i); putchar('\n');
} else
{
if (L%2==0) { puts("-1"); continue; }
printf("%d %d %d ",L+1,L+2,L);
for (RI i=L+3;i+1<=R;i+=2)
printf("%d %d ",i+1,i); putchar('\n');
}
}
return 0;
}
F. Palindromic Polynomial
題都沒看,鑑定為不可做
G. Palindromic Differences
徐神開場寫的,我題目都沒看
#include <bits/stdc++.h>
using llsi = long long signed int;
constexpr llsi mod = 1'000'000'009;
constexpr llsi $n = 500005;
constexpr llsi ksm(llsi a, llsi b) {
llsi r = 1;
while(b) {
if(b & 1) r = r * a % mod;
a = a * a % mod;
b >>= 1;
}
return r;
}
llsi fac[$n], facinv[$n], p2[$n];
void prep(int n) {
fac[0] = 1; p2[0] = 1;
for(int i = 1; i <= n; ++i) p2[i] = p2[i - 1] * 2 % mod;
for(int i = 1; i <= n; ++i) fac[i] = fac[i - 1] * i % mod;
facinv[n] = ksm(fac[n], mod - 2);
for(int i = n; i >= 1; --i) facinv[i - 1] = facinv[i] * i % mod;
return ;
}
int n, a[$n];
void work() {
std::cin >> n;
for(int i = 1; i <= n; ++i) std::cin >> a[i];
std::sort(a + 1, a + n + 1);
llsi ans = fac[n / 2];
int cnt = 1;
for(int i = 2, j = n - 1; ; ++i, --j) {
if(a[i] + a[j] != a[i - 1] + a[j + 1]) {
ans = 0;
break;
}
if(i < j && a[i] == a[i - 1]) cnt++;
else {
ans = ans * facinv[cnt] % mod;
if(a[i - 1] != a[j + 1]) ans = ans * p2[cnt] % mod;
cnt = 1;
}
if(i >= j) break;
}
if((n & 1) && a[n / 2 + 1] * 2 != a[1] + a[n]) ans = 0;
std::cout << ans << char(10);
// if(n == 14) std::cout << fac[7] * p2[7] % mod << char(10);
return ;
}
int main() {
std::ios::sync_with_stdio(false);
prep(500000);
int T; std::cin >> T; while(T--) work();
return 0;
}
H. Graph Isomorphism
神秘分類討論題
注意到和一個圖同構的圖數量在沒什麼特殊限制時大機率是 \(n!\) 級別的,因此當 \(n\) 足夠大時絕大多數圖都是無解的
手玩一波會發現菊花圖因為有一個 fixed point 的存在,因此恰好有 \(n\) 種同構圖
與此同時比較隱蔽的還有菊花圖的補圖,即一個孤點加上一個完全圖也是合法的
另外還有些特殊情況,例如點數小於等於 \(3\) 的圖、完全圖,最坑的是當 \(n=4\) 時四元環和每個點度數為 \(1\) 的圖也是有解的
#include<cstdio>
#include<iostream>
#define RI register int
#define CI const int&
using namespace std;
const int N=100005;
int t,n,m,x,y,deg[N];
int main()
{
for (scanf("%d",&t);t;--t)
{
scanf("%d%d",&n,&m);
for (RI i=1;i<=n;++i) deg[i]=0;
for (RI i=1;i<=m;++i)
scanf("%d%d",&x,&y),++deg[x],++deg[y];
if (n<=3) { puts("YES"); continue; }
if (n==4&&m==2&°[1]==1&°[2]==1&°[3]==1&°[4]==1) { puts("YES"); continue; }
if (n==4&&m==4&°[1]==2&°[2]==2&°[3]==2&°[4]==2) { puts("YES"); continue; }
if (m==1LL*n*(n-1)/2) { puts("YES"); continue; }
int center=0,outer=0;
for (RI i=1;i<=n;++i)
if (deg[i]==n-1) ++center; else if (deg[i]==1) ++outer;
if (center==1&&outer==n-1) { puts("YES"); continue; }
int iso=0,kernel=0;
for (RI i=1;i<=n;++i)
if (deg[i]==0) ++iso; else if (deg[i]==n-2) ++kernel;
if (iso==1&&kernel==n-1) { puts("YES"); continue; }
puts("NO");
}
return 0;
}
I. DAG Generation
不會 Counting 怎麼辦,我們只需要暴力打表並找規律即可輕鬆過題
首先計算相同的機率顯然更簡單,同時不妨把生成圖的方式稍作變換,先隨機固定一個排列,然後欽定邊只能從前面的點連向後面的點,不難發現這種生產方式和原題的表述等價
考慮總的方案數作為分母是 \([n!\times 2^{\frac{n(n-1)}{2}}]^2\),現在我們要對每一種具體的方案去重後平方累計貢獻得到分子
寫一個暴力會發現分子形如 \(n!\times (2^1-1)\times (2^2-1)\times (2^n-1)\),因此最後答案的表示式即為:
#include <bits/stdc++.h>
using llsi = long long signed int;
constexpr llsi mod = 1'000'000'009;
constexpr llsi $n = 500005;
constexpr llsi ksm(llsi a, llsi b) {
llsi r = 1;
while(b) {
if(b & 1) r = r * a % mod;
a = a * a % mod;
b >>= 1;
}
return r;
}
llsi fac[$n], facinv[$n], p2[$n], hkr[$n];
void prep(int n) {
fac[0] = 1; p2[0] = 1; hkr[0] = 1;
for(int i = 1; i <= n; ++i) p2[i] = p2[i - 1] * 2 % mod;
for(int i = 1; i <= n; ++i) fac[i] = fac[i - 1] * i % mod;
facinv[n] = ksm(fac[n], mod - 2);
for(int i = n; i >= 1; --i) facinv[i - 1] = facinv[i] * i % mod;
return ;
}
int main() {
std::ios::sync_with_stdio(false);
prep(100000);
hkr[1] = 1;
for(int i = 2; i <= 100000; ++i) hkr[i] = hkr[i - 1] * (p2[i] - 1) % mod;
int T; std::cin >> T; while(T--) {
llsi n; std::cin >> n;
llsi ans = hkr[n] * fac[n] % mod;
ans = ans * ksm(fac[n] * ksm(2, n * (n - 1) / 2) % mod, (mod - 2) * 2) % mod;
std::cout << (1 + mod - ans) % mod << char(10);
}
}
J. Persian Casino
小清新 Counting 題,想清楚策略後就很簡單了
注意到最優策略一定是先一直做,直到 rollback 的次數全部耗盡;接下來的部分怎麼操作都不會改變答案的期望了
因此最壞情況下,若 \(2^m<n-m\),即前 \(m\) 輪就用完了 rollback,此時積攢的錢不夠後面每輪投一塊錢,此時無解
否則必然有解,考慮計算期望,不難發現最後的錢數只和倍增的輪次有關,不妨列舉最後恰好倍增了 \(k\) 次
分情況討論,當 \(k<n\) 時,這種情形的機率為 \(C_{m-1}^{k-1}\times \frac{1}{2^k}\),錢數為 \(2^k\),因此總貢獻為 \(\sum_{k=m}^{n-1} C_{m-1}^{k-1}=C_n^m\)
當 \(k=n\) 時,改為列舉一共用了多少次 rollback,總貢獻為 \(\sum_{i=0}^{m-1} C_n^i\times \frac{1}{2^n}\times 2^n=\sum_{i=0}^{m-1} C_n^i\)
因此最後合併一下發現答案就是 \(\sum_{i=0}^{m} C_n^i\),計算十分容易
#include<cstdio>
#include<iostream>
#define RI register int
#define CI const int&
using namespace std;
const int N=100005,mod=1e9+9;
int t,n,m,fact[N],ifac[N];
inline int quick_pow(int x,int p=mod-2,int mul=1)
{
for (;p;p>>=1,x=1LL*x*x%mod) if (p&1) mul=1LL*mul*x%mod; return mul;
}
inline void init(CI n)
{
fact[0]=1; for (RI i=1;i<=n;++i) fact[i]=1LL*fact[i-1]*i%mod;
ifac[n]=quick_pow(fact[n]); for (RI i=n-1;i>=0;--i) ifac[i]=1LL*ifac[i+1]*(i+1)%mod;
}
inline int C(CI n,CI m)
{
if (n<0||m<0||n<m) return 0;
return 1LL*fact[n]*ifac[m]%mod*ifac[n-m]%mod;
}
int main()
{
for (scanf("%d",&t),init(1e5);t;--t)
{
scanf("%d%d",&n,&m);
if (m<=20&&(1<<m)<n-m) { puts("bankrupt"); continue; }
int ans=0; for (RI i=0;i<=m;++i) (ans+=C(n,i))%=mod;
printf("%d\n",ans);
}
return 0;
}
K. Determinant, or...?
計算行列式的值的經典思路就是轉為上/下三角矩陣後把對角線乘起來,因此考慮消元
不難發現原矩陣可以等分為四個部分,其中除左上角區域外的三個部分內的數完全相同
考慮把矩陣的上半部分減去下半部分,這樣就可以把右上角部分全部變為 \(0\)
而左上角部分不難發現扔滿足上述性質,遞迴處理即可將整個行列式變為下三角矩陣,總複雜度 \(O(n\log n)\)
#include <bits/stdc++.h>
constexpr int mod = 1'000'000'009;
int a[1 << 20];
int64_t calc(int l, int r) {
if(r == l + 1) return a[l];
int mid = (l + r) >> 1;
for(int i = l; i < mid; ++i) a[i] = (a[i] + mod - a[i + r - mid]) % mod;
return calc(l, mid) * calc(mid, r) % mod;
}
int main() {
std::ios::sync_with_stdio(false);
int n; std::cin >> n; n = 1 << n;
for(int i = 0; i < n; ++i) std::cin >> a[i];
std::cout << calc(0, n) << char(10);
return 0;
}
L. Directed Vertex Cacti
題都沒看,鑑定為不可做
M. Siteswap
祁神 solo 的一個神秘模擬題(?),我連題意都看不懂,好像細節挺多很容易寫掛
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N = 1e5+5;
int n, A[N];
bool vis[N];
void solve(){
cin >> n;
for (int i=0; i<n; ++i) cin >> A[i], vis[i]=false;
int ans[3] = {0, 0, 0};
for (int i=0; i<n; ++i) if (!vis[i]){
if (0==A[i]){
vis[i]=true;
continue;
}
if (A[i]%n==0){
int res = A[i]/n;
if (n%2==0) ans[i%2] += res;
else{
if (res%2==0){
ans[i%2] += (res+1)/2;
ans[(i+1)%2] += res/2;
}else ans[2] += res;
}
continue;
}
int loops = 0;
bool odd = false;
int cur = i;
while (!vis[cur]){
int res = (cur+A[cur])/n;
loops += res;
if (A[cur]%2==1) odd=true;
vis[cur]=true;
cur = (cur+A[cur])%n;
}
if (!odd){
// printf("n%2=%lld\n", n %2);
if (n%2==0) ans[i%2] += loops;
else{
if (loops%2==0){
ans[i%2] += (loops+1)/2;
ans[(i+1)%2] += loops/2;
}else ans[2] += loops;
}
}else ans[2] += loops;
}
cout << ans[0] << ' ' << ans[1] << ' ' << ans[2] << '\n';
}
signed main(){
ios::sync_with_stdio(0); cin.tie(0);
int t; cin >> t; while (t--) solve();
return 0;
}
Postscript
感覺這場題都不太對我們隊的好球區啊,這斯拉夫人怎麼和日本人一樣喜歡出一堆數學題呢