11.8 正睿做题笔记
T1
感觉还是比较水的一道题,我们直接用 bitset 硬做就可以了。
代码:
#include<bits/stdc++.h>
#define dd double
#define ld long double
#define ll long long
#define uint unsigned int
#define ull unsigned long long
#define N 20100
#define M number
using namespace std;
const int INF=0x3f3f3f3f;
template<typename T> inline void read(T &x) {
x=0; int f=1;
char c=getchar();
for(;!isdigit(c);c=getchar()) if(c == '-') f=-f;
for(;isdigit(c);c=getchar()) x=x*10+c-'0';
x*=f;
}
bitset<20100> bit[N],b0,b1,b2,b3;
char s[N];
int n,m;
int main(){
// freopen("my.in","r",stdin);
// freopen("my.out","w",stdout);
read(n);read(m);
for(int i=1;i<=m;i++){
b0.reset();b1.reset();b2.reset();b3.reset();
scanf("%s",s+1);
for(int j=1;j<=n;j++){
if(s[j]=='0') b0[j]=1;
else if(s[j]=='1') b1[j]=1;
else if(s[j]=='2') b2[j]=1;
else if(s[j]=='3') b3[j]=1;
}
for(int j=1;j<=n;j++){
if(s[j]=='0') continue;
else if(s[j]=='1') bit[j]=bit[j]^(b2|b3);
else if(s[j]=='2') bit[j]=bit[j]^(b1|b3);
else if(s[j]=='3') bit[j]=bit[j]^(b1|b2|b3);
bit[j][j]=0;
}
}
int Ans=0;
for(int j=1;j<=n;j++){
Ans+=bit[j].count();
}
assert(Ans%2==0);
Ans/=2;
printf("%d\n",Ans);
return 0;
}
T2
这个题本质是 dp 优化,首先我们考虑二分如何 check,我们考虑设 dp \(f_i\) 表示前 \(i\) 个值,在每段都大于等于 \(0\) 的情况下,最多能分成多少段。朴素 dp 是 \(n^2\) 的,我们考虑整个转移可以用 bit 优化。
代码:
#include<bits/stdc++.h>
#include<iostream>
#define dd double
#define ld long double
#define ll long long
#define uint unsigned int
#define ull unsigned long long
#define N 200010
#define M number
using namespace std;
const int INF=0x3f3f3f3f;
const dd eps=1e-4;
const dd eps2=1e-10;
template<typename T> inline void read(T &x) {
x=0; int f=1;
char c=getchar();
for(;!isdigit(c);c=getchar()) if(c == '-') f=-f;
for(;isdigit(c);c=getchar()) x=x*10+c-'0';
x*=f;
}
template<typename T> inline T Max(T a,T b){return a<b?b:a;}
int n,a[N],k;
struct BIT{
int p[N];
inline void Init(int n){
for(int i=1;i<=n;i++) p[i]=-1;
}
inline int lowbit(int x){return x&(-x);}
inline void Add(int w,int x){
for(int i=w;i<=n;i+=lowbit(i)) p[i]=Max(x,p[i]);
}
inline int GetMax(int w){
int res=-INF;
for(int i=w;i;i-=lowbit(i)) res=Max(res,p[i]);return res;
}
}bit;
inline void Init(){
read(n);read(k);
for(int i=1;i<=n;i++) read(a[i]);
}
int f[N],d[N];
dd b[N],c[N],rk[N];
inline bool Check(dd mid){
bit.Init(n);
// printf("mid=%lf\n",mid);
for(int i=1;i<=n;i++) b[i]=1.0*a[i]-mid;
// printf("b:");for(int i=1;i<=n;i++) printf("%lf ",b[i]);puts("");
for(int i=1;i<=n;i++) b[i]+=b[i-1];
for(int i=1;i<=n;i++) c[i]=b[i];
sort(c+1,c+n+1);int len=unique(c+1,c+n+1)-c-1;
// printf("c:");for(int i=1;i<=n;i++) printf("%lf ",b[i]);puts("");
for(int i=1;i<=n;i++){
int Rank=lower_bound(c+1,c+len+1,b[i])-c;
rk[Rank]=b[i];d[i]=Rank;
}
// printf("d:");for(int i=1;i<=n;i++) printf("%d ",d[i]);puts("");
for(int i=1;i<=n;i++){
if(b[i]>=0) f[i]=1;
else{f[i]=-1;continue;}
int Ans=bit.GetMax(d[i]);
f[i]=Max(f[i],Ans+1);
bit.Add(d[i],f[i]);
}
// printf("f:");for(int i=1;i<=n;i++) printf("%d ",f[i]);puts("");
return f[n]>=k;
}
inline void Solve(){
dd l=1,r=100;
while(r-l>eps){
// printf("%lf\n",l);
dd mid=(l+r)/2;
if(Check(mid)) l=mid;
else r=mid;
}
printf("%lf\n",l);
}
int main(){
// freopen("my.in","r",stdin);
// freopen("my.out","w",stdout);
Init();Solve();return 0;
}
T3
这个题是一个结论题,我们首先考虑这样一件事情,就是说,我们把每个边看做两条 “b”,这里为了区分,我们暂且称作 "b",下面也是一样,然后我们考虑这样一件事情,我们现在对 b 进行染色,每一条边对应的两条 b 我们要分别染上颜色 \(0\) 或颜色 \(1\),然后我们考虑对于一个管道方案来说,有多少中染色方案。
我们称两种染色方案相同,当且仅当对于第一种染色方案的一个管道 \(x,y\),在第二种染色方案中有一个管道 \(x,y\) 与其对应,且这两个管道的染色方式相同。
注意到,如果一个管道方案不存在相同的管道,那么染色方式就是 \(2^{n-1}\),如果存在 \(c\) 对相同的管道,那么染色方式就是 \(2^{n-1-c}\),这是因为我们考虑只有一对管道相同,那么我们交换这两个管道,方案恰好会被算重 \(2\) 倍。
我们发现 \(2^{n-1-c}\) 除以 \(2^{n-1}\) 正好是权值,所以我们只需要算出上面的染色方案,把方案数除以 \(2^{n-1}\) 次方即可。
然后我们如果直接计数这个染色方案并不是很好计算,所以我们考虑看这个染色方案与什么相对应,我们考虑把染色成 \(0\) 的强制放在左边,染色成 \(1\) 的强制放在右边,然后我们发现,一种染色方式就对应着一种边的连接方式,我们考虑直接计数这个链接方式,关注到这个链接方式唯一的限制是一条边上的两个 b 不能相连。
同时注意到,我们每个点都是独立的,也就是说,总的链接方式应该是关注到每一个点,看与每个点相邻的边是怎么相连的,然后把所有点的乘起来。
对于每个点来说相当于这样一个问题:有 \(n\) 对点,现在要把这 \(n\) 对点两两匹配,或者不匹配,有一些限制,限制是以点对的形式出现的,然后一对点之间不能相互匹配,计数上面这个东西额的方案数。
考虑 dp,设 \(f_{i,0/1/2}\) 表示有 \(i\) 对点,还有 \(0/1/2\) 个单点,其中这 \(i\) 对点之间是限制,问这些点两两匹配或不匹配的方案数。
对于 \(f_{i,0}\) 来说,我们考虑最后一对的最后一个点怎么匹配,首先这个点可能不匹配,这样方案就是 \(f_{i-1,1}\),或者匹配,但只能选前面这 \(i-1\) 对点对中的点匹配,匹配之后我们把这一对匹配的点扔掉,考虑剩下的点的方案,这样做是因为不能让匹配上的点再匹配其他点。由此,我们可以得到:
同理,我们还可以得到:
预处理完 dp 之后直接转移即可。
#include<iostream>
#include<cstdio>
#define ll long long
#define N 1000100
#define M number
using namespace std;
const int mod=1e9+7;
int f[N][3],n,ans=1,inv2=500000004,d[N];
int main(){
scanf("%d",&n);
for(int i=1;i<=n-1;i++){
int from,to;scanf("%d%d",&from,&to);d[from]++;d[to]++;
ans=1ll*ans*inv2%mod;
}
f[0][0]=1;f[0][1]=1;f[0][2]=2;
for(int i=1;i<=n;i++){
f[i][0]=(f[i-1][1]+1ll*2*(i-1)*f[i-2][2]%mod)%mod;
f[i][1]=(f[i][0]+1ll*f[i-1][1]*2*i%mod)%mod;
f[i][2]=((f[i][1]+f[i][0])%mod+1ll*f[i-1][2]*2*i%mod)%mod;
}
for(int i=1;i<=n;i++) ans=1ll*ans*f[d[i]][0]%mod;
printf("%d",ans);
return 0;
}
这个题启示我可以尝试计数相似但好计数的情况,然后考虑转化。