星期一:
ABC345的D题dfs暴搜写着太痛苦了,而且目前题解也很少,先搁置一会
牛客寒假营1的E也是暴搜,当时用的三进制枚举,这次改用dfs写
代码如下:
ll n;
int m;
int a[20],ans;
vector<int>x,y;
void dfs(int i){
if(i==m){
int res=1;
for(int i=2;i<=n;i++) res+=(a[i]>a[1]);
ans=min(res,ans);
return ;
}
a[x[i]]+=3;
dfs(i+1);
a[x[i]]-=3;
a[y[i]]+=3;
dfs(i+1);
a[y[i]]-=3;
a[x[i]]++,a[y[i]]++;
dfs(i+1);
a[x[i]]--,a[y[i]]--;
return ;
}
void solve(){
cin >> n >> m;
for(int i=1;i<=n;i++) cin >> a[i];
while(m--){
int u,v; cin >> u >> v;
if(u==1 || v==1) a[1]+=3;
else x.push_back(u),y.push_back(v);
}
ans=n,m=x.size();
dfs(0);
cout << ans << "\n";
x.clear(),y.clear();
}
学了下第一类斯特林数,S(n,m)为n个人坐m张圆桌的方案数,圆桌上的坐法为圆排列,方案数等同于(n-1)的全排列即(n-1) !
第一类斯特林数用递推打表计算
来看一下这题: 洛谷传送门
思路:高度为n的建筑在中间,左边有a-1个建筑群,右边b-1个,一个建筑群即为其中最高的建筑在外面,其余建筑任意排列,可以想到这排列方式即为第一类斯特林的圆排列,每个建筑群的顺序即为从外到内由低到高,是确定的,遂计算时不需要考虑
答案即为S(n-1,a+b-2)*C(a+b-2,a-1) ,意为从n-1个建筑中选出a+b-2个建筑群,再选a-1个在左,剩下b-1个即在右
代码如下:
const int N=5e4+10,M=210;
const int mod=1e9+7;
ll n;
ll s[N][M],c[M][M];
void init(){
s[0][0]=1;
for(int i=1;i<N;i++){
for(int j=1;j<M;j++)
s[i][j]=s[i-1][j-1]+(i-1)*s[i-1][j],s[i][j]%=mod;
}
for(int i=0;i<M;i++) c[i][0]=1;
for(int i=1;i<M;i++){
for(int j=1;j<=i;j++)
c[i][j]=c[i-1][j-1]+c[i-1][j],c[i-1][j]%=mod;
}
}
void solve(){
init();
int t; cin >> t;
while(t--){
int a,b; cin >> n >> a >> b;
ll ans=s[n-1][a+b-2]*c[a+b-2][a-1]%mod;
cout << ans << "\n";
}
}
星期二:
第二类斯特林数:
S(n,m)为n个人进m个房间的方案,房间里的人不需要排列组合
这个算法比较复杂,自己写的递归有问题,网上的资料也没看明白,拾了个买鑫鑫的代码,给封装成了函数,暂时能用就行:
ll power(ll x,ll y){
ll res=1;
while(y){
if(y&1) res=res*x%mod;
x=x*x%mod,y/=2;
}
return res;
}
ll inv(ll x){
return power(x,mod-2);
}
ll asks(int n,int m){
if(n<m) return 0;
vector<ll>f(n+1);
f[0]=1;
for(int i=1;i<=n;i++) f[i]=f[i-1]*i%mod;
ll res=0;
for(int i=0;i<=m;i++){
ll t=power(i,n)*inv(f[i])%mod*inv(f[m-i])%mod;
if(!((m-i)&1)) res+=t,res%=mod;
else res=(res-t+mod)%mod;
}
return res;
}
打了个队内天梯赛,紧接着是cf div3 round935,上分
星期三:
下午打了个中国传媒大学校赛同步赛,边上课边打,轻取樊神
题都不错,题意清晰明了,列几道
签到题: 但我想了一会
思路:正反跑两遍区间最大和的线性dp,i的最大和即为dp1[ i ]+dp2[ i ] - a[ i ]
构造题:一道有水平的构造
思路:首先不存在无解的情况,然后我们想什么地方是可以立马确定的,即是n个1的行和列,当我们把那一行一列填满后,要求一个1的行列就满足了,不能再填1,于是可选的行和列变成了n-2个,此时又可以确定n-1的行和列,如此往下,直到填好 n/2(向上取整)个行列,即结束
最后出的一道题:
思路:条件可翻译为 aj % ai ==0 , ak % aj ==0,我写了三重循环,但复杂度为调和级数
贴下代码:
const int N=2e6+10;
ll n;
int bu[N],ma;
ll ans;
void solve(){
cin >> n;
for(int i=1;i<=n;i++){
int x; cin >> x;
ma=max(x,ma);
bu[x]++;
}
for(int i=1;i<=ma;i++){
if(!bu[i]) continue;
for(int j=i;j<=ma;j+=i){
if(!bu[j]) continue;
for(int k=j;k<=ma;k+=j){
if(!bu[k]) continue;
ans+=1ll*bu[i]*bu[j]*bu[k];
}
}
}
cout << ans;
}
最后是从早上看到晚上的round 935 F题 cf传送门
思路:STL,思路很简单,我也不到为啥断断续续做了一天,一个优先队列其实就够了
星期四:
补了几题
位运算如果会爆int的话,记得用1ll << i 或者 1ull << i
贴道牛客寒假集训营1的题 牛客传送门
思路:和背包没有一点关系,对于m二进制上的每一位1,我们可以考虑将其变为0,并使所有地位变为1,这样枚举二进制的每一位,对于每一个变化过的及原来的m,若m | w[ i ] == m即能拿,最后 答案取最大的res
代码如下:
const int N=2e6+10,M=210;
const int mod=1e9+7;
ll n;
ll m;
int v[N],w[N];
ll ans;
ll ask(ll x){
ll res=0;
for(int i=1;i<=n;i++)
if((x|w[i])==x) res+=v[i];
return res;
}
void solve(){
cin >> n >> m;
for(int i=1;i<=n;i++) cin >> v[i] >> w[i];
ans=ask(m);
for(int i=31;i>=0;i--){
if(m&1<<i){
ll tmp=(m^(1<<i))|((1<<i)-1); //i位变为0,低位全变为1
ans=max(ask(tmp),ans);
}
}
cout << ans << "\n";
ans=0;
}
星期六:
电科校赛初赛,从中午到晚上坐了一天的牢,有道规律题想半天没想出来,我发现光靠脑子想还是不够的,遇事不决就打个表,打表,打表,打表!!!别搁那坐着格物致知
星期天:
补cf round936 C题: cf传送门
思路:dfs,加个二分答案,不二分光贪心是贪不了的,dfs完后对删边的数量进行判断,特判如果根节点所在连通块节点数量不够,删边数量减一
代码如下:
const int N=2e5+10;
const int mod=1e9+7;
ll n;
ll k,cnt;
vector<int>ve[N];
int dfs(int x,int fa,int ned){
ll sum=1;
for(auto i:ve[x]){
if(i==fa) continue;
sum+=dfs(i,x,ned);
}
if(sum>=ned && fa) sum=0,cnt++; //删边操作,对父节点的贡献为0
if(x==1 && sum<ned) cnt--; //根节点连通块数量不够,得少删一条边
return sum;
}
bool check(int x){
cnt=0;
dfs(1,0,x);
if(cnt>=k) return 1;
return 0;
}
void solve(){
cin >> n >> k;
for(int i=1;i<n;i++){
int u,v; cin >> u >> v;
ve[u].push_back(v);
ve[v].push_back(u);
}
int l=1,r=n;
ll ans=0;
while(l<=r){
int mid=l+r>>1;
if(check(mid)) ans=mid,l=mid+1;
else r=mid-1;
}
cout << ans << "\n";
for(int i=1;i<=n;i++) ve[i].clear();
}
半个下午做了道数位dp,调了蛮久: 洛谷传送门
思路:f [ i ][ j ][ k ]表示位数为i,最高位为j,k数码出现次数
因为要输出10个数码,所以可以跑10次dp给每个数码单独计算
这里dp( 0, 0 ) 结果也是0,因为如果数大于0,统计0时是统计不到1位最高位为0的,所以干脆把dp( 0, 0 )也设为0,方便计算
代码如下:
ll n;
ll f[20][10][10],a[20];
ll p10[20];
void init(){
p10[0]=1;
for(int i=1;i<20;i++) p10[i]=p10[i-1]*10;
for(int i=0;i<=9;i++) f[1][i][i]=1;
for(int i=2;i<20;i++){
for(int j=0;j<=9;j++)
for(int k=0;k<=9;k++){
if(j==k) f[i][j][k]=p10[i-1];
for(int y=0;y<=9;y++)
f[i][j][k]+=f[i-1][y][k];
}
}
}
ll dp(ll x,int num){
if(!x) return 0;
int cnt=0;ll res=0,tmp=x;
while(x) a[++cnt]=x%10,x/=10;
for(int i=cnt;i;i--){
int now=a[i];
for(int j=(i==cnt);j<now;j++)
res+=f[i][j][num];
if(now==num) res+=tmp%p10[i-1]+1; //固定,now==num时才有贡献
}
for(int i=1;i<cnt;i++)
for(int j=1;j<=9;j++) //位数低于cnt的数的贡献,最高位从1开始
res+=f[i][j][num];
return res;
}
void solve(){
init();
ll a,b; cin >> a >> b;
for(int i=0;i<=9;i++) cout << dp(b,i)-dp(a-1,i) << " ";
}