2026勰码金牌夏令营济南A-营模拟赛二补题报告
S12316 明宗亮
一、比赛过程
1、赛前估分
| T1 双数组构造 | T2 染色 | T3 动态规划 | T4 分形图 | 总分 |
| 10 pts | 10 pts | 10 pts | 16pts | 46 pts |
2、实际分数
| T1 双数组构造 | T2 染色 | T3 动态规划 | T4 分形图 | 总分 |
| 10 pts | 10 pts | 10 pts | 0 pts | 30 pts |
3、赛时简述
这一次模拟赛自我感觉不及格,但是比赛难度还是有的。
第一题比较难,比赛时只想到了特殊性质的分,对于正解想到了可能是dp,但是最终并没有成功写出。最终只拿到了10分。
第二题难度小于第一题,但是比赛时并没有成功AC。我先做出了特殊性质的分后开始想正解思路,比赛时正解思路灵光一现,但是比赛时不知道怎么回事被我想出反例了????,最终也没有做出来。
第三题的结论并不难推,但是求解的过程我只想到了用DFS。时间复杂度很高,最终也只拿到了10分的暴力分。
第四题题意较为好懂,考试时还画出了图,比赛时还是先做特殊性质,成功推了15分钟后结论推对了,结果被高精度直接单杀了。
二、题目分析
T1、双数组构造/construct
1、题目大意
2、赛时思路
比赛时看到构造心凉了一半,读了下题之后一点思路没有,看到特殊性质直接上了。
老师很善良的给了两个的数据,我们不难使用瞪眼发得出这种情况下的答案其实就是
。
3、特殊性质正解
#include<bits/stdc++.h> #define int long long using namespace std; int n,m,p; int ans,num,answ; signed main() { ios::sync_with_stdio(false); cin.tie(0); cout.tie(0); freopen("construct.in","r",stdin); freopen("construct.out","w",stdout); cin>>n>>m>>p; if(n<=1){ int res=((1+m)*m/2)%p; cout<<res%p; return 0; } return 0; }4、正解思路
根据“对于“任意的均满足
”这个限制,可得出
,又因为题意中所给的
分别单调不降、单调不增的条件,可以看成将序列
反过来然后与
拼接,即
。
这样题意就转化成了长度为,值域在
的单调不降整数序列计数问题,直接设
代表前
个数组成的序列中第
个数是
的方案数,就有转移
, 显然求和的这个式子可以前缀和优化。
结论:其实这题的答案就是,所以在保证
是大质数的情况下可以做到
。
5、正解
#include<bits/stdc++.h> using namespace std; char s[2][200005]; int main(){ cin.tie(0)->sync_with_stdio(0); int t;cin>>t; while(t--){ int n;cin>>n; for(int i=0;i<2;i++){ cin>>s[i]; for(int j=0;j<n;j++) s[i][j]^=48; } for(int i=0;i<n;i++) if(!s[0][i]&&!s[1][i]){ for(int j=i-1;j<=i+1;j+=2) if(0<=j&&j<n){ bool flag=0; for(int k=0;k<2;k++) if(!s[k][j]){ s[0][i]=s[1][i]=s[k][j]=flag=1;break; } if(flag) break; } } int ans=0; for(int i=0;i<2;i++) for(int j=0;j<n;j++) ans+=s[i][j]; cout<<ans<<'\n'; } return 0; }T2、染色/color
1、题目大意
2、赛时思路
比赛时想到了用贪心,但是考试时总觉得正解思路不靠谱。结果考完试讲题后又有验证了他的正确性。
我在考试时只做出了所有单元格都是白色的做法。思路是能放就尽量多放,放不了就放一个题意中的图形,这样能达到最优。
3、赛时代码
#include<bits/stdc++.h> #define int long long using namespace std; const int N=2e5+5; char a[5][N]; int n,t; bool flag=0; //char a[5][2][2]={{1,0,1,1},{1,1,1,0},{1,1,0,1},{0,1,1,1}}; signed main() { ios::sync_with_stdio(false); cin.tie(0); cout.tie(0); freopen("color.in","r",stdin); freopen("color.out","w",stdout); srand(time(0)); cin>>t; while(t--){ cin>>n; for(int i=1;i<=2;i++){ for(int j=1;j<=n;j++){ cin>>a[i][j]; if(a[i][j]=='1') flag=1; } } if(!flag){ int c=n,res=0/,res2=2n*/; while(1){ if(c-3>=0){ c-=3; res+=6; } else if(c-2>=0){ c-=2; res+=3; } else break; } cout<<res<<"\n"; continue; } cout<<(rand%(2*n))<<"\n"; } return 0; }4、正解思路
这一道题的正解有两种做法:
贪心做法:
按列枚举,对于每一列,它能够填入L需要这列的两个位置均为空,之后枚举该列相邻的个位置,至少需要一个位置为空,但是此时可能有多种填入情况。我们的策略是能填就填。
正确性可以分类讨论:
如果填到了左侧,不影响右侧的操作。
否则填到了右侧,如果右侧只有一个空位置,那么方案是唯一的,填入一定不劣。
否则右侧两个位置均为空,会消掉一个位置,另一个位置能填当且仅当它右侧那一列均为空。这种情况它上下是对称的,填入哪个位置都一样。
故该贪心得到的即为最优答案,时间复杂度:。
状压 DP 做法:
设表示处理完前
列后,最多放置的
形数量,其中
是第
列每个格子是否为剩余可用白格的状态。
由于任意两个相邻列最多只能提供个格子,而一个
形需要
个格子,因此两列之间最多只能新形成一个
形。
从转移到
时,枚举每一个变量
,分为
种:
1. 不在两列之间形成形:要求第
列的格子原封不动保留,不与第
列结合。
2. 两列之间形成顺时针旋转的
形:要求第
列上下均为白格,且第
列下方为白格。
3. 两列之间形成顺时针旋转的
形:要求第
列上下均为白格,且第
列上方为白格。
4. 两列之间形成顺时针旋转的
形:要求第
列上下均为白格,且第
列上方为白格。
5. 两列之间形成顺时针旋转的
形:要求第
列上下均为白格,且第
列下方为白格。
转移的时候将这种情况取最大值,最终答案是初始棋盘上的黑格总数
的最大值。 时间复杂度:
。
T3、动态规划/dp
1、题目大意
2、赛时思路
这一道题的结论其实是比较好推的。令,那么最后求的就是
与哪些
无关。 这相当于
贡献到
的次数是
的倍数。
所以我在比赛时DFS暴搜并统计每个的出现次数,最后判断有多少
不能整除
即可。
3、暴力代码
#include<bits/stdc++.h> #define int long long using namespace std; const int N=1e5+5; int n,m; //int t[N],ans; set<int> st; int t[N]; vector<int> ans; void dfs(int i,int j){ if(i==1){ t[j]++; // st.insert(j); return ; } if(!(2<=i&&2<=j&&i<=n&&j<=n&&i<=j)) return ; dfs(i-1,j); dfs(i-1,j-1); } signed main() { ios::sync_with_stdio(false); cin.tie(0); cout.tie(0); freopen("dp.in","r",stdin); freopen("dp.out","w",stdout); cin>>n>>m; // ans=n; dfs(n,n); // cout<<n-st.size()<<"\n"; for(int i=2;i<=n;i++){ if(t[i]%m==0) ans.push_back(i); } cout<<ans.size()<<"\n"; for(auto i:ans) cout<<i<<" "; // for(auto i:st) cout<<i<<" "; return 0; }4、正解思路
这个递推式能够让我想到组合数递推。
我们发现,对于第行第
列的元素,它要么会贡献到上一行的第
列,要么贡献到上一行的
列。
而对于,它会向上贡献
轮,但因为要贡献到
,所以它要在这
步中选择
轮向左贡献。
那么把的每次贡献看成一条从自己到
的路线,那么一共就有
种。
那么其实就是一次性判断哪些组合数是
的倍数即可。
可以直接采用暴力求组合数的方法,每次从的时候,需要乘一个数再除一个数,如果直接维护组合数
的值(只要除数与
不互质)可能会有问题。
但是我们只关心每一个组合数是否被整除,我们先将
分解质因数变成
的形式,每次做乘法的时候我们也只需要将该数质因数分解然后在对应质因子上加次数,除法就是减去,判断是否是
的倍数的条件就是对于每个
都应满足
的次数
即可。
虽然对分解质因数的复杂度是
,但一次性对
分解质因数的复杂度是可以做到
的(只需要管
的质因子即可),所以总时间复杂度为
。
4、正解代码
#include<bits/stdc++.h> using namespace std; using ll=long long; using uint=unsigned int; using ull=unsigned long long; #define endl '\n' #define lb lower_bound #define ub upper_bound #define ne nth_element #define mie min_element #define mae max_element #define eb emplace_back #define ump unordered_map #define pq priority_queue #define clz __builtin_clz #define ctz __builtin_ctz #define sz(x) (int)x.size() #define np next_permutation #define clzl __builtin_clzll #define ctzl __builtin_ctzll #define ppc __builtin_popcount #define all(x) x.begin(),x.end() #define ppcl __builtin_popcountll #define fpi(x) freopen(x,"r",stdin) #define fpo(x) freopen(x,"w",stdout) #define Time cerr<<"\nTime: "<<clock() #define uid uniform_int_distribution #define me(x,y) memset(x,y,sizeof(x)) #define seed chrono::system_clock::now().time_since_epoch().count() #ifdef Nuj template<class T> void _dbg(T h){ string s=typeid(T).name(); cerr<<" = "; bool f=s=="PKc"||s=="Pc"||s=="Ss"; if(f) cerr<<'"';if(s=="c") cerr<<(char)39; cerr<<h; if(f) cerr<<'"';if(s=="c") cerr<<(char)39; } template<class T> void _dbg(int l,const char *c,T h){ if(l) cerr<<fixed<<setprecision(10)<<"In Line "<<l<<' '; cerr<<c,_dbg(h),cerr<<endl; } template<class T,class...H> void _dbg(int l,const char *c,T h,H... a){ if(l) cerr<<fixed<<setprecision(10)<<"In Line "<<l<<' '; int t=0;bool f=0,g=0; while(*c^44||f|g|t){ if(*c==39) f=!f;if(*c==34) g=!g; if(!f&!g) t+=(*c==40)-(*c==41)+(*c==91)-(*c==93)+(*c=='{')-(*c=='}'); cerr<<*c++; } _dbg(h),cerr<<", ",_dbg(0,++c,a...); } #define dbg(...) _dbg(__LINE__,#__VA_ARGS__,__VA_ARGS__) #else #define dbg(...) 0 #define assert(...) 0 #endif #define mod 998244353 #define inf 0x3f3f3f3f bool no[100005]; int lim[100005][6],now[6],ans[100005],cnt; pair<int,int> d[6]; inline void add(int x,int v){ for(int i=0;i<cnt;i++) now[i]+=v*lim[x][i]; } int main(){ // fpi(""); // fpo(""); #ifndef Nuj cin.tie(0)->sync_with_stdio(0); #endif int n,m;cin>>n>>m; for(int i=2;i*i<=m;i++) if(!(m%i)){ int tim=0; while(!(m%i)) tim++,m/=i; d[cnt++]={i,tim}; } if(m^1) d[cnt++]={m,1}; for(int i=1;i<n;i++){ int tmp=i; for(int j=0;j<cnt;j++) while(!(tmp%d[j].first)) lim[i][j]++,tmp/=d[j].first; } int tot=0; for(int i=1;i<n;i++){ add(n-i,1),add(i,-1); bool yes=1; for(int j=0;j<cnt;j++) if(now[j]<d[j].second){ yes=0;break; } if(yes) ans[++tot]=i+1; } cout<<tot<<endl; for(int i=1;i<=tot;i++) cout<<ans[i]<<' '; return 0; }T4、分形图/fractal
1、题目大意
2、赛时思路
比赛时只想了特殊性质的正解。
我们不难画出这一道题数据较小时的图。
接着我们寻找普遍规律:
对于时,答案是
。
对于时,答案是
。
所以我们不难得出在这种情况下的答案是。
比赛时被高精度单杀了,。
3、正解思路
考虑这张画板在表示什么意思,对于阶画板,若
位二进制数
中
的最高位是
,
的最高位是
,那么
就会被涂黑,否则就去掉第
位继续递归。
换言之,若存在某一二进制位使得是
,
是
,那么它就会被涂黑,否则就会被涂白。
那么问题就变成了有多少对同时满足对于任意二进制位要么
是
,要么
是
,且
也满足要么
是
,要么
是
。
这是个裸的数位 dp,设代表从低到高
是否有来自上一位的进位的方案数,转移是平凡的。
但是本题的答案可能会达到位,所以你需要高精,时间复杂度:
。
4、正解
#include<bits/stdc++.h> using namespace std; using ll=long long; using uint=unsigned int; using ull=unsigned long long; #define endl '\n' #define lb lower_bound #define ub upper_bound #define ne nth_element #define mie min_element #define mae max_element #define eb emplace_back #define ump unordered_map #define pq priority_queue #define clz __builtin_clz #define ctz __builtin_ctz #define sz(x) (int)x.size() #define np next_permutation #define clzl __builtin_clzll #define ctzl __builtin_ctzll #define ppc __builtin_popcount #define all(x) x.begin(),x.end() #define ppcl __builtin_popcountll #define fpi(x) freopen(x,"r",stdin) #define fpo(x) freopen(x,"w",stdout) #define Time cerr<<"\nTime: "<<clock() #define uid uniform_int_distribution #define me(x,y) memset(x,y,sizeof(x)) #define seed chrono::system_clock::now().time_since_epoch().count() #ifdef Nuj template<class T> void _dbg(T h){ string s=typeid(T).name(); cerr<<" = "; bool f=s=="PKc"||s=="Pc"||s=="Ss"; if(f) cerr<<'"';if(s=="c") cerr<<(char)39; cerr<<h; if(f) cerr<<'"';if(s=="c") cerr<<(char)39; } template<class T> void _dbg(int l,const char *c,T h){ if(l) cerr<<fixed<<setprecision(10)<<"In Line "<<l<<' '; cerr<<c,_dbg(h),cerr<<endl; } template<class T,class...H> void _dbg(int l,const char *c,T h,H... a){ if(l) cerr<<fixed<<setprecision(10)<<"In Line "<<l<<' '; int t=0;bool f=0,g=0; while(*c^44||f|g|t){ if(*c==39) f=!f;if(*c==34) g=!g; if(!f&!g) t+=(*c==40)-(*c==41)+(*c==91)-(*c==93)+(*c=='{')-(*c=='}'); cerr<<*c++; } _dbg(h),cerr<<", ",_dbg(0,++c,a...); } #define dbg(...) _dbg(__LINE__,#__VA_ARGS__,__VA_ARGS__) #else #define dbg(...) 0 #define assert(...) 0 #endif #define mod 998244353 #define inf 0x3f3f3f3f #define fr(i,l,r) for(int i = (l);i <= (r);++i) #define rf(i,l,r) for(int i = (l);i >= (r);--i) #define fo(i,l,r) for(int i = (l);i < (r);++i) const int base = 1000000000; const int base_digits = 9; struct Bint { vector<int> a; int sign; Bint() : sign(1) { } Bint(long long v) { *this = v; } Bint(const string &s) { read(s); } void operator=(const Bint &v) { sign = v.sign; a = v.a; } void operator=(long long v) { sign = 1; if (v < 0) sign = -1, v = -v; for (; v > 0; v = v / base) a.push_back(v % base); } Bint operator+(const Bint &v) const //Addition Operation { if (sign == v.sign) { Bint res = v; for (int i = 0, carry = 0; i < (int) max(a.size(), v.a.size()) || carry; ++i) { if (i == (int) res.a.size()) res.a.push_back(0); res.a[i] += carry + (i < (int) a.size() ? a[i] : 0); carry = res.a[i] >= base; if (carry) res.a[i] -= base; } return res; } return *this - (-v); } Bint operator-(const Bint &v) const //Subtraction Function { if (sign == v.sign) { if (abs() >= v.abs()) { Bint res = *this; for (int i = 0, carry = 0; i < (int) v.a.size() || carry; ++i) { res.a[i] -= carry + (i < (int) v.a.size() ? v.a[i] : 0); carry = res.a[i] < 0; if (carry) res.a[i] += base; } res.trim(); return res; } return -(v - *this); } return *this + (-v); } void operator*=(int v) //Multiplication Function { if (v < 0) sign = -sign, v = -v; for (int i = 0, carry = 0; i < (int) a.size() || carry; ++i) { if (i == (int) a.size()) a.push_back(0); long long cur = a[i] * (long long) v + carry; carry = (int) (cur / base); a[i] = (int) (cur % base); //asm("divl %%ecx" : "=a"(carry), "=d"(a[i]) : "A"(cur), "c"(base)); } trim(); } Bint operator*(int v) const { Bint res = *this; res *= v; return res; } friend pair<Bint, Bint> divmod(const Bint &a1, const Bint &b1) { int norm = base / (b1.a.back() + 1); Bint a = a1.abs() * norm; Bint b = b1.abs() * norm; Bint q, r; q.a.resize(a.a.size()); for (int i = a.a.size() - 1; i >= 0; i--) { r *= base; r += a.a[i]; int s1 = r.a.size() <= b.a.size() ? 0 : r.a[b.a.size()]; int s2 = r.a.size() <= b.a.size() - 1 ? 0 : r.a[b.a.size() - 1]; int d = ((long long) base * s1 + s2) / b.a.back(); r -= b * d; while (r < 0) r += b, --d; q.a[i] = d; } q.sign = a1.sign * b1.sign; r.sign = a1.sign; q.trim(); r.trim(); return make_pair(q, r / norm); } Bint operator/(const Bint &v) const //Division Function { return divmod(*this, v).first; } Bint operator%(const Bint &v) const //Modulus Operation { return divmod(*this, v).second; } void operator/=(int v) //Shorthand Operation { if (v < 0) sign = -sign, v = -v; for (int i = (int) a.size() - 1, rem = 0; i >= 0; --i) { long long cur = a[i] + rem * (long long) base; a[i] = (int) (cur / v); rem = (int) (cur % v); } trim(); } Bint operator/(int v) const { Bint res = *this; res /= v; return res; } int operator%(int v) const { if (v < 0) v = -v; int m = 0; for (int i = a.size() - 1; i >= 0; --i) m = (a[i] + m * (long long) base) % v; return m * sign; } void operator+=(const Bint &v) { *this = *this + v; } void operator-=(const Bint &v) { *this = *this - v; } void operator*=(const Bint &v) { *this = *this * v; } void operator/=(const Bint &v) { *this = *this / v; } bool operator<(const Bint &v) const { if (sign != v.sign) return sign < v.sign; if (a.size() != v.a.size()) return a.size() * sign < v.a.size() * v.sign; for (int i = a.size() - 1; i >= 0; i--) if (a[i] != v.a[i]) return a[i] * sign < v.a[i] * sign; return false; } bool operator>(const Bint &v) const { return v < *this; } bool operator<=(const Bint &v) const { return !(v < *this); } bool operator>=(const Bint &v) const { return !(*this < v); } bool operator==(const Bint &v) const { return !(*this < v) && !(v < *this); } bool operator!=(const Bint &v) const { return *this < v || v < *this; } void trim() { while (!a.empty() && !a.back()) a.pop_back(); if (a.empty()) sign = 1; } bool isZero() const { return a.empty() || (a.size() == 1 && !a[0]); } Bint operator-() const { Bint res = *this; res.sign = -sign; return res; } Bint abs() const { Bint res = *this; res.sign *= res.sign; return res; } long long longValue() const { long long res = 0; for (int i = a.size() - 1; i >= 0; i--) res = res * base + a[i]; return res * sign; } friend Bint gcd(const Bint &a, const Bint &b) //GCD Function(Euler Algorithm) { return b.isZero() ? a : gcd(b, a % b); } friend Bint lcm(const Bint &a, const Bint &b) //Simple LCM Operation { return a / gcd(a, b) * b; } void read(const string &s) //Reading a Big Integer { sign = 1; a.clear(); int pos = 0; while (pos < (int) s.size() && (s[pos] == '-' || s[pos] == '+')) { if (s[pos] == '-') sign = -sign; ++pos; } for (int i = s.size() - 1; i >= pos; i -= base_digits) { int x = 0; for (int j = max(pos, i - base_digits + 1); j <= i; j++) x = x * 10 + s[j] - '0'; a.push_back(x); } trim(); } friend istream& operator>>(istream &stream, Bint &v) { string s; stream >> s; v.read(s); return stream; } friend ostream& operator<<(ostream &stream, const Bint &v) { if (v.sign == -1) stream << '-'; stream << (v.a.empty() ? 0 : v.a.back()); for (int i = (int) v.a.size() - 2; i >= 0; --i) stream << setw(base_digits) << setfill('0') << v.a[i]; return stream; } static vector<int> convert_base(const vector<int> &a, int old_digits, int new_digits) { vector<long long> p(max(old_digits, new_digits) + 1); p[0] = 1; for (int i = 1; i < (int) p.size(); i++) p[i] = p[i - 1] * 10; vector<int> res; long long cur = 0; int cur_digits = 0; for (int i = 0; i < (int) a.size(); i++) { cur += a[i] * p[cur_digits]; cur_digits += old_digits; while (cur_digits >= new_digits) { res.push_back(int(cur % p[new_digits])); cur /= p[new_digits]; cur_digits -= new_digits; } } res.push_back((int) cur); while (!res.empty() && !res.back()) res.pop_back(); return res; } typedef vector<long long> vll; static vll karatsubaMultiply(const vll &a, const vll &b) //Multiplication using Karatsuba Algorithm { int n = a.size(); vll res(n + n); if (n <= 32) { for (int i = 0; i < n; i++) for (int j = 0; j < n; j++) res[i + j] += a[i] * b[j]; return res; } int k = n >> 1; vll a1(a.begin(), a.begin() + k); vll a2(a.begin() + k, a.end()); vll b1(b.begin(), b.begin() + k); vll b2(b.begin() + k, b.end()); vll a1b1 = karatsubaMultiply(a1, b1); vll a2b2 = karatsubaMultiply(a2, b2); for (int i = 0; i < k; i++) a2[i] += a1[i]; for (int i = 0; i < k; i++) b2[i] += b1[i]; vll r = karatsubaMultiply(a2, b2); for (int i = 0; i < (int) a1b1.size(); i++) r[i] -= a1b1[i]; for (int i = 0; i < (int) a2b2.size(); i++) r[i] -= a2b2[i]; for (int i = 0; i < (int) r.size(); i++) res[i + k] += r[i]; for (int i = 0; i < (int) a1b1.size(); i++) res[i] += a1b1[i]; for (int i = 0; i < (int) a2b2.size(); i++) res[i + n] += a2b2[i]; return res; } Bint operator*(const Bint &v) const { vector<int> a6 = convert_base(this->a, base_digits, 6); vector<int> b6 = convert_base(v.a, base_digits, 6); vll a(a6.begin(), a6.end()); vll b(b6.begin(), b6.end()); while (a.size() < b.size()) a.push_back(0); while (b.size() < a.size()) b.push_back(0); while (a.size() & (a.size() - 1)) a.push_back(0), b.push_back(0); vll c = karatsubaMultiply(a, b); Bint res; res.sign = sign * v.sign; for (int i = 0, carry = 0; i < (int) c.size(); i++) { long long cur = c[i] + carry; res.a.push_back((int) (cur % 1000000)); carry = (int) (cur / 1000000); } res.a = convert_base(res.a, 6, base_digits); res.trim(); return res; } }dp[5005][2][2],x,y; bool X[5005],Y[5005]; int main(){ // fpi(""); // fpo(""); #ifndef Nuj cin.tie(0)->sync_with_stdio(0); #endif int K;cin>>K>>x>>y; for(int i=0;i<K;i++) X[i]=x%2,Y[i]=y%2,x/=2,y/=2; if(x!=0||y!=0) return cout<<0,0; ***dp=1; for(int i=0;i<K;i++) for(int j=0;j<2;j++) for(int k=0;k<2;k++) for(int l=0;l<2;l++) for(int p=0;p<2;p++) if((l||!p)&&(j^l^X[i]||!(k^p^Y[i]))){ dp[i+1][(j+l+X[i])>>1][(k+p+Y[i])>>1]+=dp[i][j][k]; } cout<<**dp[K]; return 0; }三、赛后总结
本次模拟赛最终得分 30 分(T1:10, T2:10, T3:10, T4:0),与赛前估分 46 分存在一定差距,主要失分点在 T4 的高精度实现和 T1、T2 的正解推导上。以下是对本次比赛的整体反思:
1. 比赛表现评估
优点:
- 策略执行到位:按照“特殊性质优先”的策略,成功拿到了 T1、T2、T3 的特殊性质分数,保证了基础得分。
- 题目理解准确:对四道题目的题意把握基本正确,没有因误解题意而浪费时间和代码。
- 暴力分稳拿:T3 的 DFS 暴力搜索实现正确,拿到了应有的 10 分暴力分。
不足:
- 正解转化能力不足:T1 虽然想到了可能是 DP,但未能成功将“单调不降/不增序列拼接”转化为“长度为 2n 的单调不降序列计数”这一关键模型。
- 贪心验证不充分:T2 比赛时已经想到了贪心思路,却因自我怀疑而放弃,赛后验证该贪心确实是正解之一。
- 高精度准备不足:T4 推对了特殊性质结论(
2^(2k-2)),但因高精度实现不熟练而直接放弃,导致 16 分丢失。 - 时间分配欠佳:在 T4 的高精度调试上花费了过多时间,影响了其他题目正解的思考。
2. 各题得失分析
T1(双数组构造):特殊性质(n=1)的公式推导正确,但正解的 DP 状态设计和前缀和优化未能独立完成。需要加强组合计数与 DP 模型之间的转化训练。
T2(染色):“全白棋盘”贪心正确,但扩展到一般情况时,对“按列枚举、能填就填”的贪心策略缺乏信心。今后应对直觉正确的贪心多进行小数据验证,而非轻易否定。
T3(动态规划):成功将问题转化为组合数C(n-1, i-1)是否被 m 整除的判断,但比赛时仅停留在暴力 DFS,未进一步想到利用质因数分解维护组合数模 m 的倍数的判定方法。数论与组合计数的结合是薄弱点。
T4(分形图):成功推出特殊性质公式,但高精度模板不熟、调试时间不足,导致最终得分为 0。暴露出在高精度、大数运算方面的代码实现能力和时间管理问题。
3. 技术收获
- 模型转化:T1 的“单调序列拼接”转化为经典 DP 计数问题。
- 贪心证明:T2 的贪心策略需要分类讨论证明,锻炼了贪心算法的证明思维。
- 组合数模判定:T3 学习了如何通过质因数分解判断组合数是否为给定数的倍数,避免了直接求组合数取模的逆元问题。
- 数位 DP 与高精度结合:T4 展示了数位 DP 如何与高精度大数计算结合,对复杂问题的完整解决方案有了更深理解。
4. 后续训练方向
- 加强高精度/大数模板熟练度:准备一套可靠的高精度加减乘除模板,并熟练应用于数位 DP、组合计数等场景。
- 提升贪心证明与验证能力:对比赛中的贪心思路,要快速编写小程序对拍验证,减少自我怀疑导致的丢分。
- 深化组合计数与数论结合:针对 T3 这类问题,系统复习组合数模运算、Lucas 定理、质因数分解技巧。
- 优化时间分配策略:设定每道题的“止损时间”,如超过 30 分钟无法推出正解,先确保暴力分拿满,再回头攻坚。
- 模拟赛复盘常态化:每次模拟赛后坚持撰写补题报告,尤其要补全所有题目的正解思路与代码,弥补知识漏洞。
总结:本次比赛虽然分数不高,但通过补题全面理解了四道题目的正解思路,暴露了在高精度、贪心验证和模型转化方面的不足。后续训练中应针对性强化这些薄弱环节,同时保持对特殊性质的敏感度,争取在下次比赛中实现分数突破。
2026/7/15