大三第三周学习笔记

周一

Guess

打了网络赛,还可以,但是不知道哪里可以补题,就写写思路吧

这道题关键就是从特例推出普遍规律,先讨论简单的情况

稍微分析一下可以发现,只有2的次幂影响结果,所以设两数为pt 2pt

t为奇数,p为2的次幂,t不影响结果所以设t=1

如果是1 2   显然2的人赢

如果是4 2   2的人说自己i dont know那么到4的时候就知道自己不是1,4的人赢

如果是4 8   4的人说自己i dont know  8的人就知道自己不是2,8的人赢

总结一下,数字大的人赢,这里有点递推的感觉。

Mutiple Set

这题不难,不要有畏难情绪,自己给自己设限

稍微推一下,设l为l/x上取整,r为r/x上取整,那么K=2^(r-l-1)x(l+r)(r-l+1)

这个式子的变量是x,l和r是题目给的。可以发现x是K的因子,对于这个式子,只要确定了x,那么可以计算l和r,那么就可以验证是否等于K

因子需要求K的所有因子,K是10^14,直接根号K枚举的话加上多组数据会T。因此考虑对K进行质因数分解,通过枚举指数来枚举因子。

这里有两种方法,一种是预处理10^7的质数,用这些质数去质因数分解,一种是大数分解。

poj 1811(素数测试和大数质因数分解)

主要是熟悉板子

Miller_Rabin素数测试可以迅速测试一个大数(long long级别)是否为质数

pollard_rho算法可以对一个long long级别的数质因数分解。

注意板子中质因数分解的结果是无序重复的素数,如24就是2 3 2 2

#include 
#include 
#include 
#include 
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

typedef long long ll;
const int S = 8;

ll mult_mod(ll a, ll b, ll c)
{
    a %= c;
    b %= c;
    ll ret = 0, tmp = a;
    while(b)
    {
        if(b & 1) 
        {
            ret += tmp;
            if(ret > c) ret -= c;
        }
        tmp <<= 1;
        if(tmp > c) tmp -= c;
        b >>= 1;
    }
    return ret;
}

ll pow_mod(ll a, ll n, ll mod)
{
    ll ret = 1;
    ll temp = a % mod;
    while(n)
    {
        if(n & 1) ret = mult_mod(ret, temp, mod);
        temp = mult_mod(temp, temp, mod);
        n >>= 1;
    }
    return ret;
}

bool check(ll a, ll n, ll x, ll t)
{
    ll ret = pow_mod(a, x, n);
    ll last = ret;
    _for(i, 1, t)
    {
        ret = mult_mod(ret, ret, n);
        if(ret == 1 && last != 1 && last != n - 1) return true;
        last = ret;
    }
    if(ret != 1) return true;
    return false;
}

bool Miller_Rabin(ll n)
{
    if(n < 2) return false;
    if(n == 2) return true;
    if((n & 1) == 0) return false;
    ll x = n - 1, t = 0;
    while((x & 1) == 0) x >>= 1, t++;

    srand(time(0));

    rep(i, 0, S)
    {
        ll a = rand() % (n - 1) + 1;
        if(check(a, n, x, t)) return false;
    }
    return true;
}

ll factor[100]; //储存质因数分解结果 有重复 无序
int tol;  //标号0~tol-1

ll gcd(ll a, ll b)
{
    ll t;
    while(b)
    {
        t = a;
        a = b;
        b = t % b;
    }
    if(a >= 0) return a;
    return -a;
}

ll pollard_rho(ll x, ll c)
{
    ll i = 1, k = 2;
    srand(time(0));
    ll x0 = rand() % (x - 1) + 1;
    ll y = x0;
    while(1)
    {
        i++;
        x0 = (mult_mod(x0, x0, x) + c) % x;
        ll d = gcd(y - x0, x);
        if(d != 1 && d != x) return d;
        if(y == x0) return x;
        if(i == k) y = x0, k += k;
    }
}

void findfac(ll n, ll k)
{
    if(n == 1) return;
    if(Miller_Rabin(n))
    {
        factor[tol++] = n;
        return;
    }
    ll p = n;
    int c = k;
    while(p >= n) p = pollard_rho(p, c--);
    findfac(p, k);
    findfac(n / p, k);
}

int main()
{
    int T; scanf("%d", &T);
    while(T--)
    {
        ll n;
        scanf("%lld", &n);
        if(Miller_Rabin(n)) puts("Prime");
        else
        {
            tol = 0;
            findfac(n, 107);
            ll ans = factor[0];
            rep(i, 1, tol)
                ans = min(ans, factor[i]);
            printf("%lld\n", ans);
        }
    }
    return 0;
}

Tree Partition(二分+贪心+树上dp)

二分答案读完题就知道了,关键是确定了最大值后,怎么操作使得删掉的边最少,这是这道题的关键。

这里要用到树形dp,dp[u]表示以u为根的子树符合条件的情况下,u所在的连通块的权值和是多少。

那么每次u自己和全部dp[v]算上,如果超过了最大值,那么就考虑删边。

为了使得删的边数最少,一次删边要去掉尽可能多的权值,所以删去最大的dp[v],还是超就删第二大的dp[v]以此类推。

这样子贪心下来就可以求出删了多少边了

#include 
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

typedef long long ll;
const int N = 2e5 + 10;
int a[N], n, k, mx, cnt;
vector g[N];
ll key, dp[N];

void dfs(int u, int fa)
{
    dp[u] = a[u];
    ll sum = a[u];
    vector ve;
    for(int v: g[u])
    {
        if(v == fa) continue;
        dfs(v, u);
        ve.push_back(dp[v]);
        sum += dp[v];
    }

    if(!ve.size()) return;
    sort(ve.begin(), ve.end(), greater());
    rep(i, 0, ve.size())
    {
        if(sum > key) sum -= ve[i], cnt++;
        else break;
    }
    dp[u] = sum;
}

bool check(ll x)
{
    cnt = 0;
    key = x;
    dfs(1, 0);
    return cnt <= k;
}

int main()
{
    int T, kase = 0; 
    scanf("%d", &T);
    while(T--)
    {
        mx = 0;
        scanf("%d%d", &n, &k); k--;
        _for(i, 1, n) g[i].clear();
        _for(i, 1, n - 1)
        {
            int u, v;
            scanf("%d%d", &u, &v);
            g[u].push_back(v);
            g[v].push_back(u);
        }
        _for(i, 1, n) scanf("%d", &a[i]), mx = max(mx, a[i]);
        
        ll l = mx - 1, r = 1e15;
        while(l + 1 < r)
        {
            ll m = l + r >> 1;
            if(check(m)) r = m;
            else l = m;
        }
        printf("Case #%d: %lld\n", ++kase, r);
    }

    return 0;
}

C. Digital Logarithm(思维)

很容易发现操作几次就变1了,考虑怎么让它最小。

注意到每次操作都是变小的,所以如果一个数是最大的,大于另一个序列的最大值,那么它一定要变小,这个是必须要操作的。因此就根据这一点,比较两者的最大值,相等就不用操作,否则就变小,用优先队列实现。

cf题解的stl用的很秀啊,代码很短。

#include 
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

int main()
{ 
    int T; scanf("%d", &T);
    while(T--)
    {
        int n; scanf("%d", &n);
        vector a(n), b(n);
        rep(i, 0, n) scanf("%d", &a[i]);
        rep(i, 0, n) scanf("%d", &b[i]);

        int ans = 0;
        priority_queue qa(a.begin(), a.end());
        priority_queue qb(b.begin(), b.end());
        while(!qa.empty())
        {
            if(qa.top() == qb.top())
            {
                qa.pop(); qb.pop();
                continue;
            }
            else if(qa.top() > qb.top())
            {
                qa.push(to_string(qa.top()).size());
                qa.pop(); ans++;
            }
            else
            {
                qb.push(to_string(qb.top()).size());
                qb.pop(); ans++;
            }
        }
        printf("%d\n", ans);
    }

    return 0;
}

但我比赛时是另一个思路,大致是先去重,然后把二位数以上的操作,再去重,然后把非1的操作。但是我写去重的时候用了很多stl,常数很大,赛时AC,赛后T了……

其实就排个序然后用两个指针即可,不用弄那么复杂。改成这么写后AC了

#include 
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

const int N = 2e5 + 10;
int a[N], b[N], visa[N], visb[N], n;
vector va, vb;

int get(int x)
{
    int res = 0;
    while(x)
    {
        x /= 10;
        res++;
    }
    return res;
}

void deal(int a[], int b[], int n)
{
    _for(i, 1, n) visa[i] = visb[i] = 0;
    sort(a + 1, a + n + 1);
    sort(b + 1, b + n + 1);

    int pa = 1, pb = 1;
    while(pa <= n && pb <= n)
    {
        if(a[pa] == b[pb])
        {
            visa[pa] = visb[pb] = 1;
            pa++; pb++;
        }
        else if(a[pa] > b[pb]) pb++;
        else pa++;
    }

    va.clear();
    vb.clear();
    _for(i, 1, n)
        if(!visa[i])
            va.push_back(a[i]);
    _for(i, 1, n)
        if(!visb[i])
            vb.push_back(b[i]);
}

int main()
{ 
    int T; scanf("%d", &T);
    while(T--)
    {
        scanf("%d", &n);
        _for(i, 1, n) scanf("%d", &a[i]);
        _for(i, 1, n) scanf("%d", &b[i]);
        deal(a, b, n);

        int ans = 0;
        rep(i, 0, va.size())
            if(va[i] >= 10)
            {
                va[i] = get(va[i]);
                ans++;
            }
        rep(i, 0, vb.size())
            if(vb[i] >= 10)
            {
                vb[i] = get(vb[i]);
                ans++;
            }
        
        int cnt = 0;
        for(int x: va) a[++cnt] = x;
        cnt = 0;
        for(int x: vb) b[++cnt] = x;
        deal(a, b, cnt);

        rep(i, 0, va.size()) 
            if(va[i] > 1)
                ans++;
        rep(i, 0, vb.size()) 
            if(vb[i] > 1)
                ans++;
        printf("%d\n", ans);
    }

    return 0;
}

周二

D. Letter Picking(dp)

之前做过一道优点类似的,也是赢平局输,用dp做的,这题也类似。

考试时用dp过的,但是写的比较复杂,但大体思路和题解是一致的

我是在状态转移分类讨论的,而题解是直接取min max的,写起来很方便

令1为Alice,0为Draw,-1为Bob

那么Alice的目标是使值变大,Bob是使值变小,所以就转化成了博弈里面的min-max搜索。

#include 
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

const int N = 2000 + 10;
int dp[N][N], n;
char s[N];

int check(char a, char b)
{
    return a > b ? 1 : (a == b ? 0 : -1);
}

int main()
{ 
    int T; scanf("%d", &T);
    while(T--)
    {
        scanf("%s", s + 1);
        n = strlen(s + 1);

        _for(i, 1, n - 1)
        {
            if(s[i] == s[i + 1]) dp[i][i + 1] = 0;  
            else dp[i][i + 1] = 1;  
        }

        for(int len = 4; len <= n; len += 2)
            _for(l, 1, n)
            {
                int r = l + len - 1;
                if(r > n) break;

                int cl = 1;
                if(dp[l + 1][r - 1] != 0) cl = min(cl, dp[l + 1][r - 1]);
                else cl = min(cl, check(s[l], s[r]));
                if(dp[l + 2][r] != 0) cl = min(cl, dp[l + 2][r]);
                else cl = min(cl, check(s[l], s[l + 1]));

                int cr = 1;
                if(dp[l + 1][r - 1] != 0) cl = min(cl, dp[l + 1][r - 1]);
                else cr = min(cr, check(s[r], s[l]));
                if(dp[l][r - 2] != 0) cl = min(cl, dp[l][r - 2]);
                else cr = min(cr, check(s[r], s[r - 1]));

                dp[l][r] = max(cl, cr);
            }

        if(dp[1][n] == 1) puts("Alice");
        else if(dp[1][n] == -1) puts("Bob");
        else puts("Draw");
    }

    return 0;
}

还有一种O(n)的做法,比赛时我就猜测Bob不可能赢,我尝试构造了Bob赢得情况没构造出来。

如果有了这个结论,那么其实只要判断什么时候平局即可,不是平局就是Alice赢

如果是平局,那么就是两边拿到字符串是一样的,考虑什么时候会一样。

因为每次拿都是两侧的,或者一侧两个,那么需要它们是一样的。

两侧一样的就是对称的,一侧两个就是aabbcc这样

那么都存在呢,可以发现可以外面是对称的,里面是aabbcc的,那么判断是否是这样的串就可以了

#include 
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

const int N = 2000 + 10;
char s[N];
int n;

int main()
{ 
    int T; scanf("%d", &T);
    while(T--)
    {
        scanf("%s", s + 1);
        n = strlen(s + 1);

        int l = 1, r = n;
        while(l < r && s[l] == s[r]) l++, r--;
        
        int flag = 1;
        for(int i = l; i <= r; i += 2)
            if(s[i] != s[i + 1])
            {
                flag = 0;
                break;
            }
        puts(flag ? "Draw" : "Alice");
    }

    return 0;
}

B. Appleman and Tree(树形dp)

这种切割的,就设在以u为根的子树中,包含u的联通块即可

还要分当前联通块是否有一个黑色,然后分类讨论即可。

注意儿子所在联通块无黑色时不能切断

#include 
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

typedef long long ll;
const int N = 1e5 + 10;
const int mod = 1e9 + 7;
vector g[N];
ll dp[N][2];
int c[N], n;

ll binpow(ll a, ll b)
{
    ll res = 1;
    for(; b; b >>= 1)
    {
        if(b & 1) res = res * a % mod;
        a = a * a % mod;
    }
    return res;
}

ll inv(ll x) { return binpow(x, mod - 2); }

void dfs(int u, int fa)
{
    ll res = 1;
    if(c[u] == 1)
    {
        for(int v: g[u])
        {
            if(v == fa) continue;
            dfs(v, u);
            if(c[v]) res = res * dp[v][1] % mod;
            else res = res * (dp[v][1] + dp[v][0]) % mod; 
        }
        dp[u][1] = res;
    }
    else
    {
        for(int v: g[u])
        {
            if(v == fa) continue;
            dfs(v, u);
            if(c[v]) res = res * dp[v][1] % mod;
            else res = res * (dp[v][1] + dp[v][0]) % mod; 
        }
        dp[u][0] = res;

        ll sum = 0;
        for(int v: g[u])
        {
            if(v == fa) continue;
            if(c[v]) sum = (sum + res) % mod;
            else sum = (sum + res * inv(dp[v][1] + dp[v][0]) % mod * dp[v][1] % mod) % mod;
        }
        dp[u][1] = sum;
    }
}

int main()
{ 
    scanf("%d", &n);
    _for(i, 2, n)
    {
        int x; scanf("%d", &x); x++;
        g[x].push_back(i);
        g[i].push_back(x);
    }
    _for(i, 1, n) scanf("%d", &c[i]);

    dfs(1, 0);
    printf("%lld\n", dp[1][1]);

    return 0;
}

周三

D. Toss a Coin to Your Graph...(删点+图上最长路径)

从提问的方式可以看出是二分答案

二分一个答案,然后先删点。删点的实现可以用一个数组标记,后面遍历点的时候都要判断这是没有被删去的点。注意新图的点数要重新计算,不是n

得到一个新图后,这时候点权没有用了,只需要判断是否存在长度大于等于k的路径

如果有环则一定存在,否则用拓扑排序加dp求最长度。判环用拓扑排序。

注意有-1的情况,二分的边界要注意一下

#include 
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

typedef long long ll;
const int N = 2e5 + 10;
vector g[N];
int a[N], d[N], dp[N], ban[N], n, m;
ll k;

bool check(int key)
{
    int cnt = 0;
    _for(i, 1, n)
    {
        if(a[i] <= key) ban[i] = 0, cnt++, dp[i] = 1;
        else ban[i] = 1;
        d[i] = 0;
    }

    _for(u, 1, n)
        for(int v: g[u])
            if(!ban[u] && !ban[v])
                d[v]++;

    queue q;
    vector topo;
    _for(i, 1, n)
        if(!ban[i] && !d[i])
            q.push(i);
    while(!q.empty())
    {
        int u = q.front(); q.pop();
        topo.push_back(u);
        for(int v: g[u])
            if(!ban[v] && --d[v] == 0)
                q.push(v);
    }
    if(topo.size() < cnt) return true;

    int res = 0;
    for(int u: topo)
        for(int v: g[u])
            if(!ban[v])
                dp[v] = max(dp[v], dp[u] + 1);
    for(int u: topo) res = max(res, dp[u]);
    return res >= k;
}

int main()
{ 
    scanf("%d%d%lld", &n, &m, &k);  
    _for(i, 1, n) scanf("%d", &a[i]);
    while(m--)
    {
        int u, v;
        scanf("%d%d", &u, &v);
        g[u].push_back(v);
    }

    int l = 0, r = 1e9 + 1;
    while(l + 1 < r)
    {
        int m = l + r >> 1;
        if(check(m)) r = m;
        else l = m;
    }
    printf("%d\n", r == 1e9 + 1 ? -1 : r);

    return 0;
}

C. Where is the Pizza?(思维)

先考虑简单的情况,c全是0

可以发现确定了一个数,就有一堆数连着被确定,形成一个环。

于是ai和bi连边,就会形成很多个环。

环中的一个点确定了就都确定了,可以确定使用a或b

因此每一个大于1的环就有两种选择

对于c已经有数,就是已经确定了,不对答案有贡献。

#include 
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

typedef long long ll;
const int mod = 1e9 + 7;
const int N = 1e5 + 10;
int f[N], a[N], b[N], ban[N], siz[N], n;

ll binpow(ll a, ll b)
{
    ll res = 1;
    for(; b; b >>= 1)
    {
        if(b & 1) res = res * a % mod;
        a = a * a % mod;
    }
    return res;
}

int find(int x) { return f[x] == x ? x : f[x] = find(f[x]); }

void Union(int x, int y)
{
    int fx = find(x), fy = find(y);
    if(fx == fy) return;
    siz[fx] += siz[fy];
    f[fy] = fx;
}

int main()
{ 
    int T; scanf("%d", &T);
    while(T--)
    {
        scanf("%d", &n);
        _for(i, 1, n) f[i] = i, ban[i] = 0, siz[i] = 1;
        _for(i, 1, n) scanf("%d", &a[i]);
        _for(i, 1, n) scanf("%d", &b[i]);

        _for(i, 1, n) Union(a[i], b[i]);
        _for(i, 1, n)
        {
            int x; scanf("%d", &x);
            ban[find(x)] = 1;
        }

        int cnt = 0;
        _for(i, 1, n)
            if(f[i] == i && !ban[i] && siz[i] > 1)
                cnt++;
        printf("%lld\n", binpow(2, cnt));
    }

    return 0;
}

C. Tokitsukaze and Two Colorful Tapes(并查集找环+贪心)

这题的关键就是像上一题一样,ai向bi连边可以形成环。如果没有做过上一题,很难想到

有环之后考虑怎么分配数字。一个直觉的想法是一大一小分配,这样让差最大,如何证明呢?

当存在a1 > a2 > a3时,对答案的贡献是等价于a1 > a3的,也就是中间的a2并没有用。那么对于任意一种分配,删去这种没有用的点后,一定是一大一小交替。而且可以发现,一大一小交替的话,个数一定是偶数。

这时大的绝对值取正,小的绝对值取负,所以我们让大的尽可能大,小的尽可能小。

因此,如果环是偶数,那么就大的位置放尽可能大的,小的放尽可能小的。

对于环是奇数,有一个位置是没有用的,所以要填一个最差的,也就是中间的值,把极值留给其他地方。

最后注意开long long

#include 
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

typedef long long ll;
const int N = 1e5 + 10;
int f[N], a[N], b[N], v[N], n, l, r, pos;
vector k[N], num;

int find(int x) { return f[x] == x ? x : f[x] = find(f[x]); }

void Union(int x, int y) { f[find(x)] = find(y); } 

ll cal(int x)
{
    if(x <= 1) return 0;
    if(x % 2 == 1) x--;
    rep(i, 0, x) v[i] = num[pos++];

    ll res = 0;
    rep(i, 0, x)
    {
        int j = (i + 1) % x;
        res += abs(v[j] - v[i]);
    }
    return res;
}

int main()
{ 
    int T; scanf("%d", &T);
    while(T--)
    {
        scanf("%d", &n);
        _for(i, 1, n) f[i] = i, k[i].clear();
        _for(i, 1, n) scanf("%d", &a[i]);
        _for(i, 1, n) scanf("%d", &b[i]);

        _for(i, 1, n) Union(a[i], b[i]);
        _for(i, 1, n) k[find(i)].push_back(i);

        ll ans = 0;
        l = 1, r = n;
        num.clear(); pos = 0;
        while(l <= r)
        {
            num.push_back(l); l++;
            num.push_back(r); r--;
        }
        
        _for(i, 1, n) ans += cal(k[i].size());
        printf("%lld\n", ans);
    }

    return 0;
}

周四

D. K-good(数学)

首先注意一波推导,注意余数取k而不是取0,因为数字要是正数,同时两边乘以2去掉分数

可以得到2n = k(k + 1 + 2t)

我自己做的时候推到这里就不知道怎么分析了

首先注意k和k+1+2t,一个是偶数一个奇数

也就是说2n可以分解为一个偶数和一个奇数相乘。

因为奇数部分没有2的因子

所以就直接把2n一直除以2来分解

然后就成了一个奇数和偶数。但是注意奇数部分不能为1,因为k和k+1+2t不可能为1

分解完之后,哪一个是k呢,注意到k+1+2t更大,所以k就取奇数和偶数更小的那个即可

#include 
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

typedef long long ll;

int main()
{ 
    int T; scanf("%d", &T);
    while(T--)
    {
        ll n;
        scanf("%lld", &n);
        n *= 2;
        
        ll t = 1;
        while(n % 2 == 0)
        {
            n /= 2;
            t *= 2;
        }

        if(n == 1) puts("-1");
        else printf("%lld\n", min(t, n));
    }

    return 0;
}

D. Not Adding(思维)

首先可以发现,新出现的数肯定是当前数子集的gcd

然后每个数都是不同的,所以当前序列没有的数很少,那么就去枚举这些输数,然后枚举它的倍数,然后将它的倍数取gcd,如果是它那么就答案加一。

#include 
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

unordered_map vis;
int n, mx;

int gcd(int a, int b) { return !b ? a : gcd(b, a % b); }

int main()
{ 
    scanf("%d", &n);
    _for(i, 1, n)
    {
        int x; scanf("%d", &x);
        vis[x] = 1;
        mx = max(mx, x);
    }

    int ans = 0;
    _for(i, 1, mx)
        if(!vis[i])
        {
            int cur = 0;
            for(int j = i; j <= mx; j += i)
                if(vis[j])
                    cur = gcd(cur, j);
            if(cur == i) ans++;
        }
    printf("%d\n", ans);

    return 0;
}

D2. Half of Same(思维)

一开始的想法是枚举当前数的因数,然后用这个因数看是多少个数的公因数

一次的复杂度是max(根号ai,因数个数 * n)  我一开始算成根号ai*n了,然后觉得会T

#include 
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

const int N = 50;
int a[N], n;
unordered_map mp;

int main()
{ 
    int T; scanf("%d", &T);
    while(T--)
    {
        mp.clear();
        scanf("%d", &n);

        int flag = 0;
        _for(i, 1, n) 
        {
            scanf("%d", &a[i]);
            if(++mp[a[i]] >= n / 2) flag = 1;
        }
        if(flag)
        {
            puts("-1");
            continue;
        }

        int ans = 1;
        _for(id, 1, n)
        {
            vector ve;
            _for(j, 1, n)
                if(a[j] - a[id] >= 0)
                    ve.push_back(a[j] - a[id]);

            if(ve.size() < n / 2) continue;

            rep(i, 0, ve.size())
            {
                for(int j = 1; j * j <= ve[i]; j++)
                    if(ve[i] % j == 0)
                    {
                        int cnt = 0;
                        rep(k, 0, ve.size())
                            if(ve[k] % j == 0)
                                cnt++;
                        if(cnt >= n / 2) ans = max(ans, j);

                        cnt = 0;
                        rep(k, 0, ve.size())
                            if(ve[k] % (ve[i] / j) == 0)
                                cnt++;
                        if(cnt >= n / 2) ans = max(ans, ve[i] / j);
                    }
            }
        }
        printf("%d\n", ans);
    }

    return 0;
}

题解的方法优美一些,把所有因子扔到map里面统计一下有无超过一半,注意相同的情况。

#include 
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

const int N = 50;
int a[N], n;
unordered_map mp, vis;

int main()
{ 
    int T; scanf("%d", &T);
    while(T--)
    {
        mp.clear();
        scanf("%d", &n);

        int flag = 0;
        _for(i, 1, n) 
        {
            scanf("%d", &a[i]);
            if(++mp[a[i]] >= n / 2) flag = 1;
        }
        if(flag)
        {
            puts("-1");
            continue;
        }

        int ans = 1;
        _for(id, 1, n)
        {
            int same = 0;
            vector ve;
            _for(j, 1, n)
            {
                if(a[j] - a[id] == 0) same++;
                else if(a[j] - a[id] > 0) ve.push_back(a[j] - a[id]);
            }

            vis.clear();
            rep(i, 0, ve.size())
            {
                for(int j = 1; j * j <= ve[i]; j++)
                    if(ve[i] % j == 0)
                    {
                       vis[j]++;
                       if(j * j != ve[i]) vis[ve[i] / j]++;
                    }
            }
            for(auto x : vis)
                if(x.second + same >= n / 2)
                    ans = max(ans, x.first);
        }
        printf("%d\n", ans);
    }

    return 0;
}

你可能感兴趣的:(ACM学习笔记,学习)