『8.21考试题解及反思』

<更新提示>

<第一次更新>


<正文>

UNO

Description

良心出题人Magolor找到了你,想要和你一起玩桌(mo)游(ni)。

Magolor: "杀蚂蚁?猪国杀?斗地主?麻将?立体图?哪一个好啊?"

你: "毒瘤出题人!"

Magolor伤心了……"我应该给人留下一个良心出题人的印象啊!"

于是Magolor选择了众所周知的UNO。整个周游只使用UNO牌,但完全不按照UNO的规则来打。牌局有3位玩家(你、Magolor、Magolor的好朋友TTL):

每个人将会摸到7张牌,然后从中选择5张组成一副手牌,最后三人中手牌大者胜。

UNO牌由无限多张0~9数字牌组成,分为RGBY四种颜色,颜色无关大小,以及一些功能牌,功能牌这里相当于废牌。

功能牌一般不参与组成任何牌种,凡是含有功能牌的手牌,无论其他牌,总是最小且相等。比如 -1 4 4 4 4 < 1 3 7 8 9, -1 3 3 4 4 =-1 -1 -1 2 3 = minimum。

其他情况手牌大小:先比较等级大的牌大,再按照等级内部比较。

Markdown

Input Format

第一行数据组数T,其后每3行一组数据,3行中每行7张牌,形式见样例输入(-1为功能牌废牌)。

第一行为你,第二行为Magolor,第三行为TTL的牌。

相邻数据之间有一组空行。

Output Format

一人胜利则输出胜利者;如果有两人并列获胜,按照类似格式输出输者;如果三人并列,输出平局,形式见样例输出。

Sample Input

7
Y1 R2 B3 G4 G5 Y1 G1
G1 G2 G3 G4 G5 Y1 G1
Y1 R2 R3 Y4 R5 Y1 G1

Y3 R1 R5 G4 Y1 -1 -1
R1 R3 R9 R0 G3 -1 -1
B3 B0 B4 B5 R1 -1 -1

R1 R3 R9 R0 G3 -1 -1
Y3 R1 R5 G4 Y1 -1 -1
B3 B2 B4 B5 R1 -1 -1

R1 R3 R9 R0 G3 -1 -1
B3 B2 B4 B5 R1 -1 -1
Y3 R1 R5 G4 Y1 -1 -1

-1 -1 -1 -1 -1 -1 -1
B0 R1 B2 R3 B4 R5 B6
G0 Y1 G2 Y3 G4 Y5 G6

B0 R1 B2 R3 B4 R5 B6
-1 -1 -1 -1 -1 -1 -1
G0 Y1 G2 Y3 G4 Y5 G6

B0 R1 B2 R3 B4 R5 B6
G0 Y1 G2 Y3 G4 Y5 G6
-1 -1 -1 -1 -1 -1 -1

Sample Output

Draw!
I w1n!
Magolor w1n!
TTL w1n!
I l0se!
Magolor l0se!
TTL l0se!

解析

考场上看问题之后直接想到的就是大模拟,分类讨论,先求出最高等级,然后再同级比较,然后敲了一个半小时才过样例,最后只有\(40\)分,\(cena\)评测的还因为用了一个\(lmabda\)表达式\(CE\)了。

其实可以有很简单的写法,就是对每一副手牌评价一个权值,然后直接按照权值比较即可,剩下的就是输赢的分类讨论了,也不是很长。

权值设计的方法就是首先等级之间的权值差一定要足够大,然后就是按照比较优先级设置不同的权值,就和数位的进制一样,然后就可以直接比较了。

\(Code:\)

#include <bits/stdc++.h>
using namespace std;
#define LL long long
const char name[3][10] = {"I","Magolor","TTL"};
const int INF = 0x3f3f3f3f;
const long long Base[] = {-INF,0,(LL)1e5,(LL)2e5,(LL)3e5,(LL)4e5,(LL)5e5,(LL)6e5,(LL)7e5,(LL)8e5,(LL)9e5};
int _buc[20],num[20]; int *buc = _buc + 1;
long long val[20];
inline int read(void)
{
    int w = 0 , x = 0; char ch = ' ';
    while ( !isdigit(ch) ) w |= ch=='-' , ch = getchar();
    while ( isdigit(ch) ) x = x*10 + ch-48 , ch = getchar();
    return w ? -x : x;
}
inline int find(int x)
{
    for (int i=9;i>=0;i--)
        if ( num[i] >= x )
            return num[i] -= x , i;
    return Base[0];
}
inline long long calc(void)
{
    memset( _buc , 0 , sizeof _buc );
    for (int i=1;i<=7;i++) buc[read()]++;
    if ( buc[-1] > 2 ) return Base[0];
    if ( buc[1] >= 2 && buc[3] && buc[4] && buc[5] ) return Base[10];
    if ( buc[1] && buc[3] && buc[5] && buc[7] && buc[9] ) return Base[9] + 1;
    if ( buc[0] && buc[2] && buc[4] && buc[6] && buc[8] ) return Base[9];
    for (int i=9;i>=4;i--)
    {
        int x = i;
        while ( x >= i - 4 && buc[x] ) x--;
        if ( x == i - 5 ) return Base[8] + i;
    }
    int a,b,c,d,e; long long ans = 0;
    memcpy( num , buc , 80 );
    a = find(5);
    ans = max( ans , a * 1LL + Base[7] );

    memcpy( num , buc , 80 );
    a = find(3) , b = find(2);
    ans = max( ans , a * 10LL + b * 1LL + Base[6] );

    memcpy( num , buc , 80 );
    a = find(4) , b = find(1);
    ans = max( ans , a * 10LL + b * 1LL + Base[5] );

    memcpy( num , buc , 80 );
    a = find(2) , b = find(2) , c = find(1);
    ans = max( ans , a * 100LL + b * 10LL + c * 1LL + Base[4] );

    memcpy( num , buc , 80 );
    a = find(3) , b = find(1) , c = find(1);
    ans = max( ans , a * 100LL + b * 10LL + c * 1LL + Base[3] );

    memcpy( num , buc , 80 );
    a = find(2) , b = find(1) , c = find(1) , e = find(1);
    ans = max( ans , a * 1000LL + b * 100LL + c * 10LL + e * 1LL + Base[2] );

    memcpy( num , buc , 80 );
    a = find(1) , b = find(1) , c = find(1) , d = find(1) , e = find(1);
    ans = max( ans , a * 10000LL + b * 1000LL + c * 100LL + d * 10LL + e * 1LL + Base[1] );
    return ans;
}
inline void solve(void)
{
    val[1] = calc() , val[2] = calc() , val[3] = calc();
    if ( val[1] == val[2] && val[2] == val[3] ) return puts("Draw!") , void();
    if ( val[1] == val[2] )
    {
        if ( val[3] < val[1] ) printf("%s l0se!\n",name[2]);
        else printf("%s w1n!\n",name[2]);
    }
    else if ( val[3] == val[2] )
    {
        if ( val[1] < val[2] ) printf("%s l0se!\n",name[0]);
        else printf("%s w1n!\n",name[0]);
    }
    else if ( val[1] == val[3] )
    {
        if ( val[2] < val[1] ) printf("%s l0se!\n",name[1]);
        else printf("%s w1n!\n",name[1]);
    }
    else if ( val[1] > val[2] && val[1] > val[3] ) printf("%s w1n!\n",name[0]);
    else if ( val[2] > val[1] && val[2] > val[3] ) printf("%s w1n!\n",name[1]);
    else if ( val[3] > val[1] && val[3] > val[2] ) printf("%s w1n!\n",name[2]);
}
int main(void)
{
    freopen("uno.in","r",stdin);
    freopen("uno.out","w",stdout);
    int T; scanf("%d",&T);
    while ( T --> 0 ) solve();
    return 0;
}

gcd

Description

众所周知,GCD的意思分别是是钢琴的索(5)、哆(1)和来(2)。因此求两个数字的最大公因数的函数 GCD(a,b) 又可以被称作索哆来函数。当然,广义索哆来函数 GCD(a,b,c,d,...)=GCD(a,GCD(b,c,d,...))。

现在Magolor有一棵n个点n-1条边的连通图状乐谱(???),乐谱中每个点有一个音高。

Magolor想求一条最长的路径,使得路径上的所有点(包括端点)的音高的广义索哆来函数值不是1。

Input Format

第一行输入正整数 。

第二行输入n个正整数,分别表示第i个点的音高ai。

接下来n-1行每行两个正整数,表示a和b之间有一条无向边。

Output Format

一个整数表示最长路径的长度(所包含的点数,包括端点)。

Sample Input

7
2 4 1 6 2 6 4
1 2
2 3
2 4
4 5
4 6
5 7

Sample Output

5

解析

看到这种\(gcd\)的题首先应该想到的是枚举因数或者倍数,然后这道题就有两种思路了。

第一个就是枚举因数,那么也就是说一条合法的路径必然有一个共同的因子,于是就可以树形\(dp\)\(f[x][d]\)代表以\(x\)为根的子树中共同包含\(x\)的第\(d\)个质因子的最长链,可以直接转移。

\[f[x][d]=\max_{y\in son(x),fac[y][d']=fac[x][d]}\{f[y][d']+1\}\]

然后顺带地更新答案即可。

第二种思路就是枚举倍数。我们可以先枚举一个节点,然后枚举这个节点权值的倍数,这样把这个节点倍数的其他节点都标记起来,然后设法有效地建立标记节点的森林,直接跑直径即可。

\(Code:\)

#include <bits/stdc++.h>
using namespace std;
const int N = 2e5+20;
inline int read(void)
{
    int x = 0 , w = 0; char ch = ' ';
    while ( !isdigit(ch) ) w |= ch=='-' , ch = getchar();
    while ( isdigit(ch) ) x = x*10 + ch-48 , ch = getchar();
    return w ? -x : x;
}
struct edge { int ver,next; } e[N*2];
int n,t,Head[N],a[N],ans;
vector < int > fac[N],f[N];
inline void insert(int x,int y) { e[++t] = (edge){y,Head[x]} , Head[x] = t; }
inline void input(void)
{
    n = read();
    for (int i=1;i<=n;i++)
        a[i] = read();
    for (int i=1;i<n;i++)
    {
        int x = read() , y = read();
        insert( x , y ) , insert( y , x );
    }
}
inline void init(void)
{
    for (int i=1;i<=n;i++)
    {
        int v = a[i];
        for (int j=2;j*j<=a[i];j++)
            if ( v % j == 0 )
            {
                fac[i].push_back( j );
                f[i].push_back( 0 );
                while ( v % j == 0 ) v /= j;
            }
        if ( v > 1 ) fac[i].push_back( v ) , f[i].push_back( 0 );
    }
}
inline void dp(int x,int fa)
{
    for (int i=Head[x];i;i=e[i].next)
    {
        int y = e[i].ver;
        if ( y == fa ) continue;
        dp( y , x );
        for (int j=0;j<fac[x].size();j++)
            for (int k=0;k<fac[y].size();k++)
                if ( fac[x][j] == fac[y][k] )
                    ans = max( ans , f[x][j] + f[y][k] ),
                    f[x][j] = max( f[x][j] , f[y][k] + 1 );
    }
}
int main(void)
{
    freopen("gcd.in","r",stdin);
    freopen("gcd.out","w",stdout);
    input();
    init();
    dp( 1 , 0 );
    printf("%d\n",ans+2);
    return 0;
}

lorem

Description

当然,人不能总是回忆过去,更要向前看。

Magolor准备为9102的选手们出一道题。当然是一道简单题,虽然2019年的选手可能不会这样认为。因为9102年与2019年相比变化很大,甚至连语言都相差甚远,Lorem Ipsum 在9102年已经成为世界唯一通用语言。显然,题目的题面是使用Lorem Ipsum 写的。

Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmodtempor incididunt ut labore et dolore magna aliqua. Ut enim ad minimveniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex eacommodo consequat. Duis aute irure dolor in reprehenderit in volupta tevelit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id
est laborum.

这份题面不仅有文采,而且颇具迷惑性。作为一名要参加NOIP9102的选手,相信你可以一眼看出解决这道题的关键就是对于q个关于这个长度为n的序列A的询问(l,r,MOD) ,分别求出区间[l,r]内所有 dolor 的 ut 值的和对 MOD 取余的结果。

Input Format

第一行两个正整数n,q,分别表示序列A的长度和询问数目。

第二行有n个正整数,即序列A。

接下来q行,每行读入 l r MOD 表示一个询问,保证1<=l<=r<=n。

Output Format

输出文件包含 q 行,每行输出一个答案表示区间[l,r]内所有 dolor 的 ut 值的和对MOD取余的结果,即「所有2^(r-l+1)个子序列的「去除重复数字以后的和」的和」

Sample Input

5 5
1 2 2 3 4
1 2 233333
2 3 333333
1 5 5
3 5 15
2 4 8

Sample Output

6
6
1
6
0

解析

首先我们必须知道一个权值在区间内的贡献,假设区间为\([l,r]\),颜色\(x\)出现了\(k\)次,那么他的贡献就是\(x(2^{r-l+1}-2^{r-l+1-k})\)

然后这道题就好做了。考虑莫队,我们可以有效地维护出现次数的数组,这是莫队很容易实现的。但是由于模数不断在变化,所以不能直接维护答案。

我们考虑用一个均摊的莫队思路,也就是利用莫队维护颜色出现次数和一些其他的信息,然后在每一个询问区间移动结束后使用分块算法,在\(O(\sqrt n )\)的时间之内求出答案即可。

如何计算答案,我们考虑分块。首先,对于一个确定的颜色我们可以直接计算贡献,但是直接枚举颜色会超时。然后我们发现颜色不同的出现次数是不会超过\(\sqrt n\),于是我们就可以在维护一个\(sum\)数组,代表出现次数为某个值的颜色的权值和,然后也可以直接计算贡献。

但是还有一个问题,即使不同的出现次数不超过\(\sqrt n\)种,但是其值的大小可能超过\(\sqrt n\),不方便枚举。于是就分一下块,当出现次数小于\(\sqrt n\)时,直接枚举计算贡献即可。大于\(\sqrt n\)时,由于出现次数大于\(\sqrt n\)的颜色不会超过\(\sqrt n\)种,直接枚举颜色计算贡献即可。

还有一个小\(trick\),就是对\(2\)的幂也分块预处理一下,方法和\(BSGS\)算法的分块是一样的,这样就能少一个\(log\)了。

\(Code:\)

#include <bits/stdc++.h>
using namespace std;
const int N = 1e5+20 , SQRTN = 500;
inline int read(void)
{
    int x = 0 , w = 0; char ch = ' ';
    while ( !isdigit(ch) ) w |= ch=='-' , ch = getchar();
    while ( isdigit(ch) ) x = x*10 + ch-48 , ch = getchar();
    return w ? -x : x;
}
struct query { int l,r,mod,id; } q[N];
int n,m,T,tot,Max,Mod,a[N],flag[N];
int cnt[N],num[SQRTN],s[SQRTN][N];
long long ans[N],_pow[2][SQRTN],sum[N];
inline void input(void)
{
    n = read() , m = read();
    for (int i=1;i<=n;i++)
        cnt[ a[i] = read() ] ++,
        Max = max( Max , a[i] );
    for (int i=1;i<=m;i++)
        q[i].l = read() , q[i].r = read() , q[i].mod = read() , q[i].id = i;
}
inline void init(void)
{
    T = sqrt(n);
    for (int i=1;i<=Max;i++)
        if ( cnt[i] > T ) num[++tot] = i , flag[i] = true;
    for (int i=1;i<=tot;i++)
        for (int j=1;j<=n;j++)
            s[i][j] = s[i][j-1] + ( a[j] == num[i] );
}
inline bool compare(query p1,query p2)
{
    if ( ( p1.l / T ) ^ ( p2.l / T ) )
        return p1.l < p2.l;
    else if ( ( p1.l / T ) & 1 )
        return p1.r < p2.r;
    else return p1.r > p2.r;
}
inline void insert(int p) { if ( flag[p] ) return; sum[cnt[p]] -= p; sum[++cnt[p]] += p; }
inline void remove(int p) { if ( flag[p] ) return; sum[cnt[p]] -= p; sum[--cnt[p]] += p; }
inline long long mul(long long a,long long b) { return a * b % Mod; }
inline void add(long long &a,long long b) { a += b; if ( a >= Mod ) a -= Mod; }
inline long long sub(long long a,long long b) { return a - b < 0 ? a - b + Mod : a - b; }
inline void calcpow(void)
{
    _pow[0][0] = _pow[1][0] = 1;
    for (int i=1;i<=T;i++)
        _pow[0][i] = mul( _pow[0][i-1] , 2 );
    for (int i=1;i<=T;i++)
        _pow[1][i] = mul( _pow[1][i-1] , _pow[0][T] );
}
inline long long quickpow(int x) { return mul( _pow[0][x%T] , _pow[1][x/T] ); }
inline void CaptainMo(void)
{
    sort( q+1 , q+m+1 , compare );
    memset( cnt , 0 , sizeof cnt );
    int l = 1 , r = 0;
    for (int i=1;i<=m;i++)
    {
        int ql = q[i].l , qr = q[i].r;
        long long res = 0;
        while ( ql < l ) insert( a[--l] );
        while ( qr > r ) insert( a[++r] );
        while ( ql > l ) remove( a[l++] );
        while ( qr < r ) remove( a[r--] );
        Mod = q[i].mod;
        calcpow();
        for (int j=1;j<=T;j++)
            add( res , mul( sum[j] % Mod , sub( quickpow(r-l+1) , quickpow(r-l+1-j) ) ) );
        for (int j=1;j<=tot;j++)
            add( res , mul( num[j] , sub( quickpow(r-l+1) , quickpow(r-l+1-s[j][r]+s[j][l-1]) ) ) );
        ans[q[i].id] = res;
    }
}
int main(void)
{
    freopen("lorem.in","r",stdin);
    freopen("lorem.out","w",stdout);
    input();
    init();
    CaptainMo();
    for (int i=1;i<=m;i++)
        printf("%d\n",ans[i]);
    return 0;
}

<后记>

猜你喜欢

转载自www.cnblogs.com/Parsnip/p/11386671.html