(杭电多校)2023“钉耙编程”中国大学生算法设计超级联赛(5)

1001 Typhoon

计算几何

对于每一个避难点,计算其到所有线段的距离,取min即可

AC代码:

#include<iostream>
#include<algorithm>
#include<cstring>
#include<vector>
#include<deque>
#include<cmath>
#include<cstdio>
#include<iomanip>
#define endl '\n'
#define eps 1e-9
//#define int long long
using namespace std;
typedef long long ll;
const int N=1e4+10,inf=2e9;
double dist[N];
//向量表示(定义一个结构体,用坐标表示向量)
struct Point {
    double x,y;
    double len() const { //模
        return sqrt(x*x+y*y);
    }
};
Point operator+(Point a,Point b) { //加法
    return {a.x+b.x,a.y+b.y};
}
Point operator-(Point a,Point b) { //减法
    return {a.x-b.x,a.y-b.y};
}
double operator&(Point a,Point b) { //点积
    return a.x*b.x+a.y*b.y;
}
double operator*(Point a,Point b) { //叉积
    return a.x*b.y-a.y*b.x;
}
//向量的点积
double dot(Point a,Point b,Point c) {
    return (b-a)&(c-a);//a,b,c均为点,向量b-a与向量c-a进行点积
}
//向量的叉积
double cross(Point a,Point b,Point c) {
    return (b-a)*(c-a);
}
int n,m;
void solve() {
    cin>>m>>n;
    vector<Point>p(m);
    for(int i=0; i<m; i++) cin>>p[i].x>>p[i].y;
    vector<Point>s(n);
    for(int i=0; i<n; i++) cin>>s[i].x>>s[i].y;
    vector<double>dis(n,inf);//定义了一个名为dis的变量,它是一个长度为n的vector容器,其中每个元素的初始值都被设置为inf
    for(int i=1; i<m; i++) {
        //枚举线段(用向量表示)
        Point a=p[i-1],b=p[i];
        //枚举避难所
        for(int j=0; j<n; j++) {
            Point c=s[j];
            double d1=dot(a,b,c);//求向量b-a与向量c-a的点积
            double d2=dot(b,a,c);//求向量c-b与向量c-a的点积
            double res;
            if(d1<0||d2<0) {
                res=min((c-a).len(),(c-b).len());//点积小于0说明角度是钝角,那么垂足就在线段外,取两点最小值
            } else {
                res=cross(a,b,c)/(b-a).len();//求点到线段的垂直距离,为向量b-a与向量c-a叉积再除以向量b-a的模(面积/底)
            }
            res=fabs(res);
            if(res<eps) res=0;//如果答案小于精度,则直接相当于等于0
            dis[j]=min(dis[j],res);
        }
    }
    cout<<setiosflags(ios::fixed)<<setprecision(4);//保留四位小数,四舍五入,头文件为#include<iomanip>
    for(int i=0; i<n; i++) cout<<dis[i]<<endl;
}
int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int t=1;
//    cin>>t;
    while(t--)
        solve();
    return 0;
}

1006 Touhou Red Red Blue

法一(dp):

AC代码:

#include<iostream>
#include<algorithm>
#include<cstring>
#include<vector>
#include<cstdio>
#define endl '\n'
#define get(a,b) (a*4+b)
//#define int long long
using namespace std;
typedef long long ll;
const int N=1e5+10,M=16;
int f[N][M];//f[i][j]表示当前球为i,两个袋子的球的状态为j的最大得分,袋子1中的球有4种状态,袋子2中的球也有4种状态,一共4*4=16种状态(0代表没有球,1~3代表球的颜色为R,G,B)
void solve() {
    string s;
    cin>>s;
    int n=s.size();
    for(int i=1; i<=n; i++) {
        int c;//表示当前球的颜色,1代表R,2代表G,3代表B
        if(s[i-1]=='R') c=1;
        else if(s[i-1]=='G') c=2;
        else c=3;
        for(int j=0; j<M; j++) f[i][j]=f[i-1][j]; //可以选择存储或放弃UFO,我们选择扔掉它
        //枚举1号袋子的状态和2号袋子的状态,0代表没有球,1~3代表分别代表球的颜色为R,G,B
        for(int a=0; a<=3; a++) {
            for(int b=0; b<=3; b++) {
                //如果两个袋子都不为空
                if(a&&b) {
                    //如果3个球的颜色均相同,那么三个球都消掉,得1分,和消消乐一样
                    if(a==b&&b==c) {
                        //得到一个附加的球放在袋子1里,此时袋子2为空
                        for(int d=1; d<=3; d++) f[i][4*d]=max(f[i][4*d],f[i-1][get(a,b)]+1);
                    }
                    //如果三个球的颜色均不相同,那么3个球都会消失然后会得到两个附加的球,放到袋子1和袋子2中
                    else if(a&&b&&a!=b&&a!=c&&b!=c) {
                        //枚举袋子1的球的颜色
                        for(int d=1; d<=3; d++) {
                            //枚举袋子2的球的颜色
                            for(int e=1; e<=3; e++) {
                                f[i][get(d,e)]=max(f[i][get(d,e)],f[i-1][get(a,b)]);
                            }
                        }
                    }
                    //否则,扔掉袋子1中的球,将袋子2中的球移到袋子1中,并将当前球放到袋子2中
                    else f[i][get(b,c)]=max(f[i][get(b,c)],f[i-1][get(a,b)]);
                }
                //if(a&&b)不满足,说明其中一个袋子是空的,
                //如果a不空,b空,那么就把c放到第二个袋子里
                else if(a) f[i][get(a, c)] = max(f[i][get(a, c)], f[i - 1][get(a, b)]);
                //如果a,b袋子均为空,那么就把c放到第一个袋子里
                else f[i][get(c, 0)] = max(f[i][get(c, 0)], f[i - 1][get(a, b)]);
            }
        }
    }
    int res=0;
    for(int i=0; i<M; i++) res=max(res,f[n][i]);
    cout<<res<<endl;
}
int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    memset(f[0],-0x3f,sizeof f[0]);//初始化为负无穷,因为后面要取max
    f[0][0]=0;//初始状态得分为0,由此开始递推
    int t=1;
    cin>>t;
    while(t--)
        solve();
    return 0;
}

法二(贪心):

AC代码:

#include<iostream>
#include<algorithm>
#include<cstring>
#include<vector>
#include<cstdio>
#define endl '\n'
//#define int long long
using namespace std;
typedef long long ll;
void solve() {
    string s;
    cin>>s;
    int n=s.size();
    int r=0,g=0,b=0;
    int res=0;
    int t=0;//表示这一轮可以自己选几个球,初始为0
    for(int i=0;i<n;){
        //这轮可以自选一个球放在袋子1中,然后看后面两个球颜色是否相同
        if(t==1){
            if(i==n-1) break;//自选的1个球加上最后一个球一共也才2个球,不足三个球是不能消消乐的,不可以得分了,直接跳出
            if(s[i]==s[i+1]) t=1,res++;//,如果后两个球颜色相同,那么将这个自选的球颜色变得和后两个球颜色一样,使得三个消消乐消掉,+1分,下一轮进入t=1的状态,即下一轮可以自选一个球放到袋子1中
            else t=2;//如果后两个球颜色不相同,那么就使得三个球颜色均不相同,下一轮进入t=2的状态,即下一轮可以自选2个球分别放到袋子1和袋子2中
            i+=2;//跳过本次和下一次,因为连续三个球都消失了
        }
        //这轮可以自选两个球放在袋子1和袋子2中,我们完全可以使得这两个球和当前球颜色相同,然后消消乐消掉这三个球,+1分,下一轮
        else if(t==2){
            t=1;//凑成3个颜色相同的球,消消乐,下一轮进入t=1的状态
            res++;
            i++;//跳过本此
        }
        //只有初始状态为t=0,然后接下来就是在t=1和t=2两状态之间转来转去
        else{
            if(s[i]=='R') r++;
            else if(s[i]=='G') g++;
            else b++;
            if(r&&g&&b) t=2;//如果三种颜色的球的数量都不为0,那么说明三个球颜色均不相同,那么进入t=2的状态,下一轮可以自选两个球
            else if(r==3||b==3||g==3) t=1,res++;//如果三个球颜色均相同,那么消消乐,+1分,进入t=1的状态,下一轮可以自选一个球
            i++;//继续下一轮
        }
    }
    cout<<res<<endl;
}
int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int t=1;
    cin>>t;
    while(t--)
        solve();
    return 0;
}

1007 Expectation(Easy Version)

算期望

根据样例:

一共有n局比赛

通项=组合数(n局中赢哪x局)*n局赢x局的概率*(1^m+2^m+...x^m)

然后遍历1到n,将其全部加起来

也就是:

假设赢一局的概率为p

C(n,1)*p^1*(1-p)^(n-1)*1^m+C(n,2)*p^2*(1-p)^(n-2)*(1^m+2^m)+...C(n,n)*p^n*(1^m+2^m+...+n^m)

由此,我们可以将其分为三个部分A,B以及C

然后呢,每一部分我们都可以不断地累加或者累乘

我们可以发现组合数每次都可以在上一项C(n,i)的基础上乘(n-i)/(i+1)

对于概率这一部分也是同样如此:

再次转换: 

发现每次都是乘a*(b-a)^(-1)

分母始终都是b^n,所以先不用管分母,只需要当全部算完之后,最后除以一个b^n就行了

最后一部分相比之下就很简单,直接加(i+1)^m

AC代码:

#include<iostream>
#include<algorithm>
#include<cstring>
#include<vector>
#include<deque>
#include<cmath>
#include<cstdio>
#define endl '\n'
#define int long long
using namespace std;
typedef long long ll;
const int mod=998244353;
int n,m,a,b;
inline int qmi(int a,int k){
    int res=1;
    while(k){
        if(k&1) res=(ll)res*a%mod;
        a=(ll)a*a%mod;
        k>>=1;
    }
    return res;
}
inline int inv(int x){
    return qmi(x,mod-2);
}
int solve() {
    cin>>n>>m>>a>>b;
    int res=0;
    int k=a*inv(b-a)%mod;
    int A=n%mod,B=qmi(b-a,n-1)*a%mod,C=1;
    for(int i=1;i<=n;i++){
        (res+=A*B%mod*C%mod)%=mod;
        A=A*(n-i)%mod*inv(i+1)%mod;
        (B*=k)%=mod;
        (C+=qmi(i+1,m))%=mod;
    }
    return (res*inv(qmi(b,n)))%mod;
}
signed main() {
    ios::sync_with_stdio(false);
    cin.tie(0);cout.tie(0);
    int t=1;
    cin>>t;
    while(t--)
    cout<<solve()<<endl;
    return 0;
}

1012 Counting Stars 

如图,比如说想要求以1为顶点的2-star图的数量,发现顶点1有三条边,那么就是在三条边中选择两条边的方案数,这样就用到了组合数学

原本我想的是分别求每一个cnt[i],对于每一个cnt[i],枚举所有的顶点,将每个顶点所形成的i-star图的个数加起来,但是会发现这样双重循环就超时了

for (int i = 2; i <= n - 1; i++) {
        for (int j = 1; j <= n; j++) {
            (cnt[i] += c[e[j].size()][i]) %= mod;
        }
    }

可以换一种思路:枚举所有的顶点,然后我们知道该顶点的度数,就是每枚举一个顶点,就记录以该顶点可以形成的2-star,3-star...的数量,这样做的好处是不会超时,因为根据握手定理

握手定理:

所以度数之和为2*m,为2e6,也就是说时间复杂度不会超过2e6 

    for(int i=1;i<=n;i++){
        for(int j=2;j<=d[i];j++){
            (ans[j]+=C(deg[i],j))%=mod;
        }
    }

 可以这样理解,加一个cnt++,然后cnt加的次数不会超过2e6

for(int i=1;i<=n;i++){
        for(int j=2;j<=d[i];j++){
            (ans[j]+=C(deg[i],j))%=mod,cnt++;
        }
    }

现在主要就是求组合数的问题,因为n太大了,所以求组合数不好求

我们发现C(n,1)=n/1,C(n,2)=n*(n-1)/(1*2),C(n,3)=n*(n-1)/(1*2*3)

所以我们可以先预处理阶乘以及阶乘的逆元

AC代码:

#include<iostream>
#include<algorithm>
#include<cstring>
#include<vector>
#include<deque>
#include<cmath>
#include<cstdio>
#define endl '\n'
//#define int long long
using namespace std;
typedef long long ll;
const int N=1e6+10,mod=1e9+7;
int d[N];
int fac[N],ifac[N];
int ans[N];
int n,m;
//快速幂
int qmi(int a,int k){
    int res=1;
    while(k){
        if(k&1) res=(ll)res*a%mod;
        a=(ll)a*a%mod;
        k>>=1;
    }
    return res;
}
//求组合数
int C(int n,int m){
    if(n<m) return 0;
    return 1ll*fac[n]*ifac[m]%mod*ifac[n-m]%mod;
}
void solve() {
    cin>>n>>m;
    for(int i=1;i<=n;i++) d[i]=ans[i]=0;
    for(int i=0;i<m;i++){
        int u,v;
        cin>>u>>v;
        d[u]++,d[v]++;
    }
    for(int i=1;i<=n;i++){
        for(int j=2;j<=d[i];j++){
            (ans[j]+=C(d[i],j))%=mod;
        }
    }
    int res=0;
    for(int i=2;i<=n-1;i++) res^=ans[i];
    cout<<res<<endl;
}
signed main() {
    ios::sync_with_stdio(false);
    cin.tie(0);cout.tie(0);
    //预处理阶乘以及阶乘的逆元
    fac[0]=ifac[0]=1;//0的阶乘是1,0的阶乘的逆元也是1
    for(int i=1;i<N;i++) fac[i]=1ll*fac[i-1]*i%mod;//预处理阶乘
    ifac[N-1]=qmi(fac[N-1],mod-2);//先求出fac[N-1]的逆元,由于(n+1)!=n!*(n+1)==>n!^(-1)=(n+1)!^(-1)*(n+1),所以也可以通过递推得到阶乘的逆元
    for(int i=N-2;i>=1;i--) ifac[i]=1ll*ifac[i+1]*(i+1)%mod;
    int t=1;
    cin>>t;
    while(t--)
    solve();
    return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值