20210620总结
T1:
题意:
一共有 \(n\) 个数,这些数可以组成 \(2^n-1\) 个组合,在全部组合中,我们在其中挑选不超过 \(m\) 个数,问总共有多少种组合。
思路:
这个题一开始是枚举组合的情况,但是这样有点难算,我们因此转换思路,枚举数字。
例如 \(A,B,C\) 我们希望出现 \(A\) ,那么就有 \(A\) , \(AB\) , \(AC\) , \(ABC\) 四种情况成立。
我们推导一下,对于出现的数字,我们可以表示成 $$\summ_{i=1}Ci_n$$
因为这个数字必须出现,那么其他数字就可以随机排列,一共有 \(2^{n-i}\) 个( \(i\) 是出现的数字个数)。
就这样,公式推导成 $$\summ_{i=1}Ci_n *2^{n-i}$$
(本来这个公式十分优美,但是我考试的时候想多了用组合数去算......只得了30分)
我们可以预处理\(\sum^m_{i=1}C^i_n\)。\(C_n^1=n,C_n^2=n*(n-1)/2....\) 以此类推,算出从 \(1-m\) 的阶乘的逆元,然后乘积即可,式子为:
inv[0]=inv[1]=1;
for(int i=2;i<=maxn;i++) inv[i]=(mod-mod/i)*inv[mod%i]%mod;
因此每一项的答案就是: $$ans=(ans+inv[i]mul(n(n-1)*...(n-i+1)))%mod$$
代码就不放了。
T2:
题意:
一共有 \(n\) 个物品,每个物品有重量。有几个容量为 \(k\) 的背包,求需要的背包数量-1。
思路:
一眼背包\(dp\) ,注意从大到小优化:
代码:
#include<bits/stdc++.h>
using namespace std;
#define ll long long
int w[25];
ll a[25],n,k;
bool dfs(int x,int bag){
for(int i=1;i<=bag&&i<=x;i++){
if(w[x]+a[i]<=k){
a[i]+=w[x];
if(x==n) return true;
if(dfs(x+1,bag)) return true;
a[i]-=w[x];
}
}
return false;
}
bool cmp(int x,int y){
return x>y;
}
int main()
{
freopen("lighthouse.in","r",stdin);
freopen("lighthouse.out","w",stdout);
cin>>n>>k;
for(int i=1;i<=n;i++) scanf("%d",&w[i]);
sort(w+1,w+n+1,cmp);
for(int i=1;i<=n;i++){
memset(a,0,sizeof(a));
if(dfs(1,i)){
cout<<i-1<<endl;
break;
}
}
//system("pause");
return 0;
}
T3
题意:
一共有 \(n\) 个数,求两两乘积为整数的数对个数。
思路:
这个题一开始看有思路,但是后来不知道如何解决,就写了个20分的特判。
考完发现思路是正确的,就是有一步的处理有一些问题。
我们先考虑把小数转化成整数。用一个 \(tag\) 记录一下小数位数,这里我们使用 \(char\) 类型进行读入,因为 \(float\) 会丢失精度。
小数要想成为整数,必须有 \(2,5\) 存在。我们对于一个数对,可以记录其所含有的 \(2,5\) 的因数的次幂。
假设两个数 \(x,y\) ,各含有的 \(2,5\) 因数次幂为 \(a,b,c,d\) ,那么就要求 \(a+b\ge0,c+d\ge0\) 。
因为小数位数最多只有 \(9\) 位,因此我们通过一个二位数组 \(cnt[i][j]\) 记录 \(2,5\) 的幂次为 \(i,j\) 的个数的数字。
输入一个新的数 \(x\) 后,进行枚举从 \(i——10,j——10\) ,答案加和即可。
代码:
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int N=205;
char s[N];
ll n,t5,t2,ans=0;
ll cnt[N][N],val=0,lim2,lim5;
void calc(ll &cnt2,ll &cnt5){
int len=0,tag=0,flag=1; cnt2=cnt5=val=0;
scanf("%s",s+1);
len=strlen(s+1);
for(int i=1;i<=len&&s[i]!=‘.‘;i++) val=val*10+(s[i]-‘0‘),flag++;
flag++;
for(int i=flag;i<=len;i++) val=val*10+(s[i]-‘0‘),tag++;
while(val%5==0&&val) val/=5,++cnt5;
while(val%2==0&&val) val/=2,++cnt2;
cnt5-=tag,cnt2-=tag;//退位
lim2=max(lim2,cnt2+50);lim5=max(lim5,cnt5+30);
}
int main()
{
freopen("check.in","r",stdin);
freopen("check.out","w",stdout);
cin>>n;
for(int i=1;i<=n;i++){
calc(t2,t5);
t2+=50,t5+=30;
for(int i=60-t5;i<=lim5;i++) //桶暴力枚举
for(int j=100-t2;j<=lim2;j++) ans+=cnt[i][j];
cnt[t5][t2]++;
}
cout<<ans<<endl;
return 0;
}
T4:
题意:
给你 \(n\) 个点和 \(n-1\) 条边和每个点的权值,求出从每个节点到其他节点的逆序对个数。
思路:
一眼看换根 \(dp\) ,但是空有思路,没法写,因此考试只做出来部分分。
求子树中权值比当前节点小的点,运用子树差分和树状数组。
然后我们考虑知道了 \(f_x\),对于 \(x\) 的子节点 \(y\),\(f_x\) 怎么向 \(f_y\) 转移。
那么在 \(sontree[x]\) 之外的点会额外考虑 \(y\) 的贡献,也就是子树外的权值比 \(c_y\) 小的,这部分贡献要加上。
在\(sontree[x]\)之内的点不会再经过 \(x\),那么就是要求在子树内比 \(c_x\) 小的,这部分贡献要消除。
这一步也可以用子树差分和树状数组求。
因为子树差分没学所以先搁置着,到时候再说。
代码:
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define lowbit(x) x&-x
const int N=1e5+5;
int nxt[N<<1],ver[N<<1],head[N<<1],tot;
int n,m,ans;
int val[N],tr[N],b[N],sizes[N],son[N];
void add(int x,int y){
ver[++tot]=y;
nxt[tot]=head[x];
head[x]=tot;
}
void query(int x,int y){
for(;x<=m;x+=lowbit(x)) tr[x]+=y;
}
int ask(int x){
int res=0;
for(;x;x-=lowbit(x)) res+=tr[x];
return res;
}
void dfs(int x,int fa){
sizes[x]=1;
int last=son[x]=ask(val[x]-1);query(val[x],1);
for(int i=head[x];i;i=nxt[i]){
int y=ver[i];
if(y==fa) continue;
dfs(y,x);
int res=ask(val[x]-1)-son[x];
sizes[x]+=sizes[y];
son[x]+=res;
ans+=res*(n-sizes[y]);
}
son[x]=son[x]-last;
}
signed main()
{
freopen("carnival.in","r",stdin);
freopen("carnival.out","w",stdout);
cin>>n;m=n;
for(int i=1;i<=n;i++) scanf("%lld",&val[i]),b[i]=val[i];
sort(b+1,b+1+m);
m=unique(b+1,b+1+m)-(b+1);
for(int i=1;i<=n;i++){
int x=lower_bound(b+1,b+1+m,val[i])-b; val[i]=x;
}
for(int i=1,x,y;i<n;i++){
scanf("%lld%lld",&x,&y);
add(x,y);add(y,x);
}
dfs(1,0);
for(int i=1;i<=n;i++) ans+=sizes[i]*(ask(val[i]-1)-son[i]);
cout<<ans<<endl;
//system("pause");
return 0;
}