upc 个人训练赛第三场:排课表(容斥原理+组合数学)

问题 A: 骑车还是走路

题目描述
在石油大学校园里,没有自行车,上课办事会很不方便.但实际上,并非去办任何事情都是骑车快,因为骑车总要找车、开锁、停车、锁车等,这要耽误一些时间。假设找到自行车,开锁并车上自行车的时间为27秒;停车锁车的时间为23秒;步行每秒行走1.2米,骑车每秒行走3.0米。请判断走不同的距离去办事,是骑车快还是走路快。
输入
第一行为待处理的数据的数量n。
其后每一行整数为一次办事要行走的距离,单位为米,距离大于0。
输出
对应每个整数,如果骑车快,输出一行"Bike";如果走路快,输出一行"Walk";如果一样快,输出一行"All"。
样例输入 Copy
4
50
90
120
180
样例输出 Copy
Walk
Walk
Bike
Bike

水题

int n,d;
double a,b;
int main()
{
    cin >> n;
    while(n--)
    {
        cin >> d;
        a = d/1.2;
        b = d/3.0+50;
        if(a > b)    cout << "Bike" << endl;
        else if(a < b)   cout << "Walk" << endl;
        else    cout << "All" << endl;
    }
    return 0;
}

问题 B: 数字方阵

题目描述
周末,爸爸妈妈带着笑笑去商场玩,正好碰上了商场在举办有奖竞答活动,而且根据问题的难度不同,奖品的价值也有不同。
其中有个问题是:在一个数字方阵中,随机放置了0 ~ 9的数位。我们把由数字1 ~ 9的组成的串称为非零串(不含0的数字符串)。例如02303230,这组数据中有23和323两种非零串。其中长度最长的非零串为323。
由于这个方阵太大了,现在请你编程计算,已知一个n*m(1<=n,m<=1000)的数字方阵,求方阵第k行的最长非零串的长度。
如下图:在4 * 8的方阵中,第2行的最长非零串长度为3。

upc 个人训练赛第三场:排课表(容斥原理+组合数学)_第1张图片

输入
输入数据有若干行。第一行,有三个整数N、M(1<=N、M<=1000)和K(1<=K<=N),其中N、M分别表示这个数字方阵中行数和列数。K表示求数字方阵第K行的最长数字符串长度。
接下来有N行,每行M个0~9的数字,每个数字间用一个空格隔开。
输出
输出第k行的最长非零串的长度。
样例输入 Copy
4 9 3
9 2 0 0 3 4 2 5 0
4 0 2 3 0 3 2 3 1
3 8 3 2 9 0 7 5 1
1 0 3 1 0 0 6 6 0
样例输出 Copy
5

思路:
把要求的那一行数据放到数组里,如果遇到的是零长度更新为0,否则就++,同时求max

int a[1005][1005];
int b[1005];
int n,m,k,ans,cnt;
int main()
{
    cin >> n >> m >> k;
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
            cin >> a[i][j];
    for(int i=1;i<=m;i++)
        b[i] = a[k][i];
    for(int i=1;i<=m;i++)
    {
        if(b[i] == 0)   cnt = 0;
        else
        {
            cnt++;
            ans = max(ans,cnt);
        }
    }
    cout << ans << endl;
    return 0;
}

问题 C: 辣椒炸弹

题目描述
植物大战僵尸这款游戏中,有一种植物武器叫辣椒炸弹,在草坪中的任意一格摆放它可以把草坪中该行上的所有僵尸瞬间消灭,也就是说,如果在第i行中任意位置摆放一个炸弹,第i行中的所有僵尸就瞬间都被杀死了。现在我们假定草坪有r行c列,草坪中有n只僵尸,僵尸不移动,现在给你k个樱桃炸弹,要求只能使用这k个炸弹来消灭这些僵尸,请问最多可以杀死多少只僵尸?
输入
第一行4个正整数r,c,k,n;
接下来n行,每行两个正整数x,y,表示第x行的第y列中有一只僵尸。
输出
第1行输出最多可以杀死的僵尸数;
第2行按顺序输出所有被消灭的行,如果有不同方案,输出字典序最小的那种方案。
样例输入 Copy
4 6 2 6
1 3
2 3
3 1
4 4
4 5
4 6
样例输出 Copy
4
1 4
提示
样例说明:可以杀死第1行和第4行的所有僵尸,方案(1,4),(2,4),(3,4)都是一样多的僵尸,但(1,4)的字典序最小。

对于30%的数据,0 对于100%的数据,0

思路:
边输入边记录当前行的僵尸个数,然后排序即可

int r,c,k,n,x,y;
int ans;
int vis[maxn];
struct node
{
    int sum;
    int id;
}book[maxn];
 
bool cmp(node x,node y)
{
    if (x.sum > y.sum) return true;
    if (x.sum == y.sum && x.id < y.id) return true;
    return false;
}
 
int main()
{
    cin >> r >> c >> k >> n;
    for(int i=1;i<=n;i++)
    {
        cin >> x >> y;
        book[x].sum++;//记录下每行的僵尸数量
    }
    for(int i=1;i<=r;i++)    book[i].id = i;
    sort(book+1,book+1+r,cmp);
    //按照僵尸数量升序排列
    for(int i=1;i<=k;i++)
        ans += book[i].sum;
    cout << ans << endl;
    for(int i=1;i<=k;i++)
        vis[i] = book[i].id;
    sort(vis+1,vis+1+k);//字典序排列
    for(int i=1;i<=k;i++)
        printf("%d ",vis[i]);
    printf("\n");
    return 0;
}

问题 D: 排座位II

题目描述
为了迎接“五一”国际劳动节,笑笑所在学校决定举行庆祝活动,活动在报告厅举行,每位学生都分到了1个座位号,而报告厅的座位是按座位号蛇形排列的,学生必须对号入座。如下图是报告厅4行*5列的座位排列情况。

现在存在的一个问题是,如何让学生根据自己的座位号很快的知道自己所坐的位置(也就是座位号所在的行、列)。

输入
第一行:包括两个数,r和c(1<=r,c<=100),表示报告厅座位共有r行c列。
第二行:一个整数n(1<=n<=r*c),代表有n个学生想知道自己的具体位置。
接下来n行:每一行一个整数,代表询问具体位置的座位号。

输出
输出n行,每一行两个整数,代表询问的座位号所在的行和列。
样例输入 Copy
4 5
3
4
13
19
样例输出 Copy
1 4
3 3
4 2

思路:
先按照第一场的打印方阵写一个蛇形方阵出来,然后匹配就直接输出

int r,c,n,x,p,tot;
int a[105][105];
int main()
{
    cin >> r >> c >> n;
    p = 0,tot = 1;
    for(int i=1;i<=r;i++)
    {
        if(p == 0)
        {
            for(int j=1;j<=c;j++)
            {
                a[i][j] = tot++;
                p = 1;
            }
        }
        else
        {
            for(int j=c;j>=1;j--)
            {
                a[i][j] = tot++;
                p = 0;
            }
        }
    }
    while(n--)
    {
        cin >> x;
        for(int i=1;i<=r;i++)
        {
            for(int j=1;j<=c;j++)
            {
                if(x == a[i][j])
                    printf("%d %d\n",i,j);
            }
        }
    }
    return 0;
}

问题 E: 排课表

题目描述
新学期伊始,作为玉米高中的教务主任W某,又要安排学生们的课程表了。

W某想要知道所有可能的排课表方案,于是他开始在纸上列举所有方案,然而在写满了一摞A4纸后,他发现可能的方案太多了——用尽玉米高中所有的A4纸都写不完。

W某最终放弃了列举所有方案的想法,但他对排课表的方案数产生了兴趣。他的组合数学不太好,所以他找到了正在玉米高中就读的你,请你帮帮TA。

简单地说,玉米高中共有T个班级。

对于其中一个班级i,这个班级每天要上mi节互不相同的课,一共有ni节课可供选择,但这ni节课不能随便安排,其中也有一些限制:
·有ai节课不能安排在第一节上
·有bi节课不能安排在最后一节上
·没有任何一节课既不能在第一节上又不能在最后一节上
你需要求出每个班级排课表的方案数除以998244353的余数。
输入
第1行包含一个正整数T,表示玉米高中的班级数。

第2行到第T+1行,每行包含四个整数,第i+1行的四个整数ni,mi,ai,bi,分别表示班级i可选的课程数,一天的课程数,不能在第一节上的课程数,不能在最后一节上的课程数。
输出
输出T行,第i行表示班级i的排课表方案数除以998244353的余数。
样例输入 Copy
【样例1】
1
3 2 0 1
【样例2】
1
5 3 1 1
样例输出 Copy
【样例1】
4
【样例2】
39
提示
样例1解释
设3节可选的课为a,b,c,其中c不能排在最后一节
4种排课表的方案分别为:ab,ba,ca,cb

所有测试数据满足
·1≤T≤104
·2≤mi≤ni≤105
·ai+bi≤ni

思路:
我尝试用高中排列组合的知识去解这道题
没错 我失败了

之后从大佬那里了解到了容斥原理,听她讲完突然惊醒
好像高中老师也是这么讲的
直接去计算每一种情况太难了,要讨论很多种情况,这里我们逆向思考一下
用总的方案数 - a在第一位时的方案数 - b在最后一位时的方案数
这里我就想起了我的高中老师讲的,我们在计算a在第一位时的方案数时,把b在最后一位的情况也计算进去了,同理计算b的时候也是,所以我们在这个地方计算了两次,只需要在最后再加上这个情况的方案数就好了
最后就推出这样的一个公式

C(n,m) * A(m,m) - C(a,1) * C(n-1,m-1) * A(m-1,m-1) - C(b,1) * C(n-1,m-1) * A(m-1,m-1) + C(a,1) * C(b,1)*C(n-2,m-2) * A(m-2,m-2)

看起来好繁琐的样子
总的排列数这个很好理解,就是从n种课里选m节在今天上,然后从a中选一节在第一位,然后还有n-1种不同的课,需要选m-1,b同理,最后从a中选1节在第一位,b中选一节在最后一位,还剩n-2种,需要选m-2节课

数据范围很大,需要预处理阶乘和逆元

const int N = 100010;
const int mod = 998244353;
/** keep hungry and keep calm! **/

ll ans;
int t,n,m,a,b;
ll fac[N];//阶乘
ll ni[N];//逆元
ll power(ll a,ll b,ll p)//快速幂 
{
    ll ans = 1,base = a;
    while(b)
	{
        if(b & 1)
        //&运算当相应位上的数都是1时,该位取1,否则该为0
            ans = 1ll*ans*base % p;
        base = 1ll*base*base % p;
        b >>= 1;//十进制下每除10 整数位就退一位
    }
    return ans;
}

void init()
{
    fac[0] = 1;
    ni[0] = 1;
    for(int i=1; i<N; i++)
	{
        fac[i] = fac[i-1]*i % mod;//求阶乘 
        ni[i] = ni[i-1]*power(i,mod-2,mod) % mod;
    }
}

int main()
{
    init();
    cin >> t;
    while(t--)
    {
    	cin >> n >> m >> a >> b;
    	ans = (fac[n]%mod*ni[n-m]%mod-fac[n-1]%mod*ni[n-m]%mod*(a+b)%mod+fac[n-2]%mod*ni[n-m]%mod*a*b%mod) % mod;
    	if(ans < 0)	ans += mod;
    	cout << ans << endl;
	}
    return 0;
}

问题 F: 玉米田

题目描述
玉米中学的学生社会实践的内容是去玉米田中种玉米。

玉米中学有n块不同的玉米田,这些玉米田编号从1到n,且第i号玉米田与第i+1号玉米田相邻,特殊地,第n号玉米田与第1号玉米田相邻。

现在玉米中学购置了k种不同的玉米,为了美观,学校要求相邻的玉米田中不能种植同一种玉米,现在W某想要知道种植玉米的方案总数。

由于W某耐心有限,因此只需要你求出对20011021取模后的结果即可。
输入
一行两个整数n,k,表示玉米田的数量和玉米的种类数。
输出
一行一个整数,表示种植玉米的方案数对20011021取模后的结果。
样例输入 Copy
【样例1】
4 2
【样例2】
4 3
样例输出 Copy
【样例1】
2
【样例2】
18
提示
样例1解释
设2种玉米为a,b
2种种植玉米的方案为:abab,baba

所有数据满足:n,k≤109

ll n,k,ans;
const int N = 100010;
const ll m = 20011021;
ll fac[N];//阶乘
ll ni[N];//逆元
ll power(ll a,ll b,ll p)//快速幂 
{
    ll ans = 1,base = a;
    while(b)
    {
        if(b & 1)
            ans = 1ll*ans*base % p;
        base = 1ll*base*base % p;
        b >>= 1;//十进制下每除10 整数位就退一位
    }
    return ans;
}
 
int main()
{
    cin >> n >> k;
    ans = ((k-1)*power(-1,n,m) % m + power(k-1,n,m)) % m;
    cout << ans << endl;
    return 0;
}

你可能感兴趣的:(upc第一阶段训练)