HZOJ
写在前面
连着三天吃三坨。本来想着今天大凶忌参加模拟赛然后没模拟赛挺好的,然后7:57临时通知加场,难道这就是大凶?好吧打就打吧,没想到真差点爆零。粗看没一道题可做怀疑自己的水平了然后赛后猛然醒悟是自己蠢如猪。其实这篇前面应该还有两篇,但是奈何这套改完得比较快就先写这套的吧。明天就可以润出学校回家了芜湖~~~~~~~~~~
《Never Ending Story》
손 닿을 수 없는 저기 어딘가 手触不到的某处오늘도 난 숨 쉬고 있지만
虽然我今天也依然呼吸着
너와 머물던 작은 의자 위에
在与你共同停留过的小小椅子上
같은 모습의 바람이 지나네
熟悉的风又一次吹过
너는 떠나며 마치 날 떠나가 듯이
你离去时仿佛连我也要一同抛弃
멀리 손을 흔들며
远远地挥手道别
언젠가 추억에 남겨져 갈 거라고
说这回忆终有一天也会随风逝去
그리워 하면 언젠가 만나게 되는
如果思念的话迟早会见面
어느 영화와 같은 일들이 이뤄져가기를
愿像某部电影一般的情节终有一天成真
힘겨워한 날에 너를 지킬 수 없었던
艰难的日子里我未能守护你
아름다운 시절 속에 머문 그대이기에
因为你曾在我最美的时光里停留
T1 选彩笔(rgb)
RT。因为我是彩笔所以我被筛选出来了。题意大概是给定\(n\) 个元素每个元素有3个关键字,求选取\(k\) 个元素使这\(k\) 个元素三个关键字的极差的最大值最小。
初看是道dp,但是选与不选有后效性,而且只能想出来\(O(n^2)\) 的假式子。然后开始乱猜结论,猜了个选取的\(k\) 个元素对于某一个关键字排序后一定在一个连续段上。然后搓了棵权值线段树然后过了两个小样例,大的过不了,结论显然假了。然后观察到子任务只与关键字的值域\(V\) 有关,猜测是\(O(nV)\) 或\(O(kV)\) 的dp,显然没想出来怎么写。然后就跳了,此时已过去1.5-2小时。
转机在写完所有题后剩下的最后5分钟。本来想抱怨不给部分分的结果突然意识到\(O(V^3)\) 可过,空间也开得下,甚至还能再加个二分。所以立马开始抢时间。事实证明没抢过。然后赛后发现那就是正解思路。无如语。
思路就是将每个元素按3个关键字的大小放在空间直角坐标系里,然后可以二分每一维的最大距离,枚举每一维的起点,计算该空间范围内点是否超过了\(k\),复杂度\(O(nV^3logV)\)。显然可以前缀和优化。对于值域内每个位置求一个三维前缀和即可。
代码
#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+10;
int n,kk;
int a[258][258][258],mxr,mxg,mxb;
int main(){freopen("rgb.in","r",stdin);freopen("rgb.out","w",stdout);ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);cin>>n>>kk;for(int i=1,r,g,b;i<=n;i++) cin>>r>>g>>b,++r,++g,++b,a[r][g][b]++,mxr=max(mxr,r),mxg=max(mxg,g),mxb=max(mxb,b);int l=0,r=max({mxr,mxg,mxb}),rr=r,mid;for(int i=1;i<=r;i++)for(int j=1;j<=r;j++)for(int k=1;k<=r;k++) a[i][j][k]+=a[i-1][j][k]+a[i][j-1][k]+a[i][j][k-1]-a[i-1][j-1][k]-a[i][j-1][k-1]-a[i-1][j][k-1]+a[i-1][j-1][k-1];//,cout<<a[i][j][k]<<'\n';while(l<=r){mid=(l+r)>>1;bool flg=0;for(int i=mid;i<=rr;i++)for(int j=mid;j<=rr;j++)for(int k=mid;k<=rr;k++)if(a[i][j][k]-a[i-mid][j][k]-a[i][j-mid][k]-a[i][j][k-mid]+a[i-mid][j-mid][k]+a[i][j-mid][k-mid]+a[i-mid][j][k-mid]-a[i-mid][j-mid][k-mid]>=kk){flg=1;break;}if(flg) r=mid-1;else l=mid+1;}cout<<l-1;return 0;
}
T2 兵蚁排序(sort)
看到是道构造题还要进行一些莫名其妙的操作就觉得不可做跳了。最后写完能写的部分说打点部分分然后突然发现那个范围是个人都能做然后就开始狂写狂调。期间还由于没意识到空间开小了以为闹鬼了狂惊不止。好在最后是调出来了。
题意就是给定一个初始序列和一个目标序列,每次操作能选择一个区间进行从小到大排序,求问能否通过\(n^2\) 以内次操作将初始序列变为目标序列,如果可以,求问可能的最小操作次数和具体操作。
笑如死本来说打打暴力看到\(n^2\) 猛然醒悟这不就可以模拟冒泡排序吗。然后写完就可以A了。然后记得存步骤的时候要开\(n^2\) 的空间,不然就会莫名其妙地挂掉。
代码没啥难度就不放了。
T3 人口局 DBA(dba)
一眼数位dp。题意是类似于填数游戏,给一个长为\(l\) 的序列,求问长度是\(l\) 的,上界是\(m\) 的,各位数之和是\(sum_l\) 的,且\(m\) 进制下大小小于\(l\) 的序列(可存在前导零)个数。
手搓了个记忆化搜索然后怕超数据范围开了unordered_map(实际拖累了效率)。反正只有15分。赛后加了点剪枝喜提56pts。
记忆化搜索
#include<bits/stdc++.h>
using namespace std;
const int maxn=2010,mod=1e9+7;
typedef long long ll;
int m,l,sum;
int a[maxn];
int dp[maxn][2][maxn*8];
#define min(a,b) (a<b?a:b)
inline int dfs(int lv,bool up,int res){if(res==0||(!up&&res==(l-lv)*(m-1))) return 1;if(res>(l-lv)*(m-1)) return 0;if(dp[lv][up][res]) return dp[lv][up][res];if(lv==l) return (res==0);int ans=0;if(up) ans=(ans+dfs(lv+1,up,res-a[lv+1]))%mod;for(register int i=min((!up?m:a[lv+1]),res+1)-1;i>=0;i--) ans=(ans+dfs(lv+1,0,res-i))%mod;return dp[lv][up][res]=ans;
}
int main(){freopen("dba.in","r",stdin);freopen("dba.out","w",stdout);ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);cin>>m>>l;for(int i=1;i<=l;i++) cin>>a[i],sum+=a[i];cout<<((dfs(0,1,sum)-1)%mod+mod)%mod;return 0;
}
然后是正解。正解是出人意料而又极为恶心的 容 斥。
由于所有序列只分两种情况:受上界限制和不受上界限制。所以我们可以分别讨论。设 \(H(i,j)\) 代表前\(i\) 个位置总和为\(j\) 的方案数。先不考虑\(l\) 的上界限制。但是我们有\(m\) 的限制,所以我们令\(F(x)\) 为恰好有\(x\) 个位置不满足\(<m\) 的方案数,\(G(x)\) 为至少有\(x\) 个位置不满足\(<m\) 的方案数。然后就是一点也记不住的二项式反演的式子:
考虑求解\(G(x)\)。考虑使用插板法。先选出\(x\)个位置等于\(m\),剩下的值再分配给各个位置就满足了“至少”。所以式子就为:
反演得:
所以\(H(l,sum)\) 即为 \(F(0)\):
类别数位dp考虑上界。考虑前面有\(x\) 个位置到达了上界且前缀和\(s\),则后面就不用考虑到达上界了所以方案数为\(H(l-x,sum-s)\)。经过一系列化简和优化(一笔带过是因为我看不懂)就为:
代码
#include<bits/stdc++.h>
using namespace std;
const int maxn=2010,mod=1e9+7;
typedef long long ll;
int m,l,sum;
int a[maxn],ny[maxn*maxn],jc[maxn*maxn];
inline int qpow(int x,int y){int res=1;while(y){if(y&1) res=1ll*res*x%mod;x=1ll*x*x%mod;y>>=1;}return res;
}
inline int C(int x,int y){if(x<y) return 0;return 1ll*jc[x]*ny[y]%mod*ny[x-y]%mod;
}
int main(){freopen("dba.in","r",stdin);freopen("dba.out","w",stdout);ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);cin>>m>>l;jc[0]=ny[0]=1;for(int i=1;i<=m*l;i++) jc[i]=1ll*jc[i-1]*i%mod,ny[i]=qpow(jc[i],mod-2);for(int i=l;i>=1;i--) cin>>a[i],sum+=a[i];int ans=0;for(int i=l;i>=1;i--){int res=0;for(int j=0;j<i;j++){int opt=(j&1)?-1:1;int k=(1ll*C(i-1,j)*((C(sum-1ll*m*j+i-1,i-1)-C(1ll*sum-a[i]-m*j+i-1,i-1))%mod)%mod+mod)%mod;res=(res+1ll*opt*k)%mod;}ans=(ans+res)%mod;sum-=a[i];}cout<<(ans%mod+mod)%mod;return 0;
}
T4 银行的源起(banking)
题意是一棵树每个点有个点权,每条边有个边权,求问在树上选取两个点,使所有点的点权乘以距离其最近的选取点的和最小,求问这个最小值是多少。
不是很想改正解所有写写部分分写法吧。
第一档 枚举选择的两个点,复杂度\(O(n^3)\)。
第二档 一个点只会去两点中的一个,所以必定会有一条边不会被任何点经过。枚举这条边将整棵数分为两部分,两部分分别计算其最小值相加即可,复杂度\(O(n^2)\)。
后面不想写了、、、