\[\text{暑假集訓CSP提高模擬}\int^{6}_{0}\frac{x^{2}}{6}dx
\]
A.駭客
顯然這個題裡只有 \(999\) 放在複雜度裡是有可能對的,要麼是 \(999^{2}\) 要麼是 \(999^{2}\log 999\),顯然應該是前者.
考慮列舉全部的最簡分數,然後乘上去,算的時候直接算當前分母/分子是最簡分式的幾倍(注意上下取整的選擇),然後在分子分母的可取值區間取一個交集即可.
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int p=1e9+7;
signed main(){
// freopen("T1.in","r",stdin);
// freopen("test.out","w",stdout);
int a,b,c,d,ans=0;
scanf("%lld %lld %lld %lld",&a,&b,&c,&d);
for(int i=1;i<=999;++i){
for(int j=1;j<=999-i;++j){
if(__gcd(i,j)==1){
int l1=ceil(a*1.0/i),l2=ceil(c*1.0/j);
int r1=floor(b*1.0/i),r2=floor(d*1.0/j);
ans=(((i+j)*max(0ll,(min(r1,r2)-max(l1,l2)+1))%p)+ans)%p;
// if(min(r1,r2)-max(l1,l2)>0)
// cout<<i<<" "<<j<<" "<<max(l1,l2)<<" "<<min(r1,r2)<<" "<<endl;
}
}
}
printf("%lld\n",ans);
}
B.密碼技術
你說得對但是這題我做過😢
注意到交換具有傳遞性,對 \((1,2),(2,3)\) 可交換的情況,可以考慮如下交換方式得到 \((1,3)\) :
1 2 3 3
2 -> 1 -> 1 -> 2
3 3 2 1
多摸幾組可以得到規律:這樣的交換是有傳遞性的,每當我們想交換兩個不能直接交換的元素,僅僅需要以中間元素作為中介分別交換即可.
因為這個題元素不重複(話說當時發現元素重複的這個題在同階範圍下是不可做的)因此行和列交換互不影響,直接乘法原理乘一下就行了. 對於每一個能互相交換的連通塊,不妨用並查集維護大小 \(s\),對答案的貢獻即為 \(A^{s}_{s}=s!\)
#include<bits/stdc++.h>
using namespace std;
#define int long long
int n,K;
int a[51][51];
const int p=998244353;
class dsu{
private:
int fa[51],size[51];
public:
void clear(){
for(int i=1;i<=n;++i){
fa[i]=i;
size[i]=1;
}
}
int find(int id){
if(id==fa[id]) return id;
fa[id]=find(fa[id]);
return fa[id];
}
void join(int x,int y){
int a=find(x),b=find(y);
if(a!=b){
fa[a]=b;
size[b]+=size[a];
size[a]=0;
}
}
bool isfa(int id){
return id==fa[id];
}
int get_size(int id){
return size[id];
}
};
dsu h,l;
int frac[51];
signed main(){
// freopen("T2.in","r",stdin);
frac[0]=1;
for(int i=1;i<=50;++i){
frac[i]=frac[i-1]*i%p;
}
scanf("%lld %lld",&n,&K);
for(int i=1;i<=n;++i){
for(int j=1;j<=n;++j){
scanf("%lld",&a[i][j]);
}
}
h.clear();l.clear();
for(int i=1;i<=n;++i){
for(int j=i+1;j<=n;++j){
bool flag=true;
for(int k=1;k<=n;++k){
if(a[i][k]+a[j][k]>K){
flag=false;break;
}
}
if(flag) h.join(i,j);
}
}
for(int i=1;i<=n;++i){
for(int j=i+1;j<=n;++j){
bool flag=true;
for(int k=1;k<=n;++k){
if(a[k][i]+a[k][j]>K){
flag=false;break;
}
}
if(flag) l.join(i,j);
}
}
int ans=1;
for(int i=1;i<=n;++i){
if(h.isfa(i)){
ans=ans*frac[h.get_size(i)]%p;
}
if(l.isfa(i)){
ans=ans*frac[l.get_size(i)]%p;
}
}
printf("%lld\n",ans);
}
C.修水管
暴力做法是顯然的,寫一個深搜暴力轉移即可(其實能寫狀壓的,我怎麼老是忘了能狀壓)
#include<bits/stdc++.h>
using namespace std;
#define double long double
int n,r;
int w[251];
double p[251];
double dfs(int now,int brok,double nowp){
double res=0;
if(now>r){ return 0;}
if(nowp==0){ return 0;}
double zc=0;if(brok){ res+=w[brok];zc=p[brok];p[brok]=0;}
double nwp=1;
for(int i=1;i<=n;++i){
res+=dfs(now+1,i,nwp*p[i]);
nwp*=(1-p[i]);
}
res+=dfs(now+1,0,nwp);
if(brok){ p[brok]=zc;}
return res*nowp;
}
int main(){
int cases;cin>>cases;while(cases--){
cin>>n>>r;
for(int i=1;i<=n;++i){
cin>>p[i]>>w[i];
}
printf("%.10Lf\n",dfs(0,0,1));
}
}
這道題的正解是 DP,設計 \(f_{i,j}\) 表示前 \(i\) 箇中選 \(j\) 個的機率,則最終的 \(h_i\) 可以透過列舉轉移得出
要得出轉移,必須要知道第 \(i\) 個數被選的機率 而這個東西等於 \(1\) 減去它不被選的機率,而不被選的機率是 \((1-p_i)^{r-j}\),表示可能選到它的 \(r-j\) 次都沒選到(根據定義,有 \(j\) 次選的前面的數,因此不算在裡面)
所以有:
\[f_{i,j}=(1−(1−p_{i})^{r−j+1})f_{i−1,j−1}+(1−p_{i})^{r−j}f_{i−1,j}
\]
最後機率乘權值即可.
#include<bits/stdc++.h>
using namespace std;
int n,r;
double ans,p[251],w[251],f[251][251];
int main(){
int cases;scanf("%d",&cases);while(cases--){
scanf("%d %d",&n,&r);
for(int i=1;i<=n;++i){
scanf("%lf %lf",&p[i],&w[i]);
}
memset(f,0,sizeof f);
ans=0;
f[0][0]=1;
double res=0;
for(int i=0;i<=n-1;++i){
for(int j=0;j<=(i<r?i:r);++j){
res=pow(1-p[i+1],r-j);
f[i+1][j]+=f[i][j]*res;
if(j<r){
f[i+1][j+1]+=f[i][j]*(1-res);
ans+=f[i][j]*(1-res)*w[i+1];
}
}
}
printf("%.10lf\n",ans);
}
}