【题解】 2016 ACM/ICPC Asia Regional Dalian Online (2+5)

本文总结了多项算法竞赛题目,包括不同圈排列问题、子数组最大公约数查询、足球比赛得分合法性验证、二分图完美匹配等,提供了详细的解题思路与代码实现。

赛时只出了两题 09 10
06卡死了。很烂,没有及时弃疗06,最后得不偿失
丢人。

1001 Different Circle Permutation(矩快+polya+欧拉函数)

题目大意:n个座位围成一圈,n个人中挑出若干个人坐下,要求相邻座椅不可同时有人。n个座位均匀分布,即相邻座位与圆心夹角为 2πN 。问共有多少种方案,旋转相同算一种。

问题可转化为环上n个点涂色,黑白两色,要求相邻点不可同时为黑。

若不考虑相邻不可同为黑,就是经典的染色问题,旋转相同为同构,对称相同不算同构。

polya定理可解,为1/|G|*(mC(π1)+mC(π2)+mC(π3)+…+mC(πk)).

由于n很大,所以用欧拉函数求gcd。

f(n)=1nni=12ngcd(i,n)=1nd|n2nφ(nd)d

此上是考虑黑白随意涂色。

题目限制黑色不可相邻。其实对于给定的n,f(n) = f(n-1)+f(n-2)
设黑色为1,白色为0
考虑新加点为0 f(n-1)合法,则新加点两边状态为 00 01 10
可组成 (0 0 0) (0 0 1) (1 0 0)
对于f(n-2) n的基础上扣去两个点,两边状态为 00 01 10
则可组成 (0 10 0) (0 10 1) (1 01 0)

至此对于n个点的所有情况都考虑到了。

这样带入之前公式。即为
f(n)=1nni=1f(gcd(i,n))=1nd|nφ(nd)f(d)

枚举n的因子,在线求欧拉函数,乘上矩快求出的方案数即可。

代码如下:

#include <iostream>
#include <cmath>
#include <vector>
#include <cstdlib>
#include <cstdio>
#include <climits>
#include <ctime>
#include <cstring>
#include <queue>
#include <stack>
#include <list>
#include <algorithm>
#include <map>
#include <set>
#define LL long long
#define Pr pair<int,int>
#define fread(ch) freopen(ch,"r",stdin)
#define fwrite(ch) freopen(ch,"w",stdout)

using namespace std;
const int INF = 0x3f3f3f3f;
const int msz = 10000;
const int mod = 1e9+7;
const double eps = 1e-8;

LL Pow_m(LL a,int b)
{
    LL ans = 1;
    while(b)
    {
        if(b&1) ans = (ans*a)%mod;
        b >>= 1;
        a = (a*a)%mod;
    }
    return ans;
}

LL eular(LL n)
{
    LL ans = n;

    for(int i = 2; i*i <= n; ++i)
    {
        if(n%i) continue;

        ans -= ans/i;
        while(n%i == 0) n /= i;
    }

    if(n > 1) ans -= ans/n;
    return ans;
}

struct Matrix
{
    LL ans[3][3];

    void init()
    {
        memset(ans,0,sizeof(ans));
        ans[0][0] = ans[0][1] = ans[1][0] = ans[2][1] = 1;
    }

    void init(int pos)
    {
        memset(ans,0,sizeof(ans));
        for(int i = 0; i < 3; ++i) ans[i][i] = 1;
    }

    Matrix operator *(const struct Matrix a)const
    {
        Matrix tmp;
        memset(tmp.ans,0,sizeof(tmp.ans));

        for(int i = 0; i < 3; ++i)
            for(int j = 0; j < 3; ++j)
                for(int k = 0; k < 3; ++k)
                    tmp.ans[i][j] = (tmp.ans[i][j]+ans[i][k]*a.ans[k][j]%mod)%mod;

        return tmp;
    }

    void prt()
    {
        for(int i = 0; i < 3; ++i)
        {
            for(int j = 0; j < 3; ++j)
            {
                printf("%lld ",ans[i][j]);
            }
            puts("");
        }
    }
};

Matrix ans,a;

LL f(int b)
{
    if(b == 1) return 1;
    if(b == 2) return 3;
    b -= 3;
    ans.init(1);
    a.init();
    while(b)
    {
        if(b&1) ans = ans*a;
        b >>= 1;
        a = a*a;
    }

    //ans.prt();
    return (4*ans.ans[0][0]+3*ans.ans[0][1]+ans.ans[0][2])%mod;
}

LL solve(int n)
{
    if(n == 1) return 2;
    LL ans = 0;

    LL d;

    for(d = 1; d*d < n; ++d)
    {
        if(n%d) continue;
        ans = (ans+eular(d)*f(n/d)%mod+eular(n/d)*f(d)%mod)%mod;
    }

    if(d*d == n) ans = (ans+eular(d)*f(d)%mod)%mod;

    return ans*Pow_m(n,mod-2)%mod;
}

int main()
{
    //fread("");
    //fwrite("");

    int n;

    while(~scanf("%d",&n))
    {
        printf("%lld\n",solve(n));
    }

    return 0;
}

1002 Different GCD Subarray Query(离线+树状数组)

题目大意:n个数,q次查询,每次查询[l,r]中不同的区间gcd个数。

fzu一场月赛里有,解法一样。当时还写了题解、!GG

看到其他巨巨有用两个vector搞的。考虑gcd的单调性,所以vector里类似一个单调栈,然后就保证了无重之类的。很赞。

我照着敲了,无限RE……后来改成一种map的写法。还是RE……再后来自己跑数据,测出sort挂了……cmp函数<=会挂,<即没事……

然后vector写法WA了……map写法A了……说说我的map思路吧,vector思路网上挺多了。感觉map比较好明白,不过就是时间可能差一点。

考虑把询问预存下来,按右边界排序,然后遍历右边界,过程中求出每个左边界到当前右边界的gcd种数,这样当前右边界的询问中每个左边界可以求一个答案出来。gcd种数可以用树状数组存。

那么存下每种gcd在当前右边界下,最近(最靠右)的左边界即可。

这就是用到map的地方,存储每种gcd的最大的左边界。

这里用到三个map
map1:遍历到当前位置,每种gcd的最大左边界
map2:遍历到当前位置i,右边界为i-1的gcd的最大左边界
map3:i-1 -> i的转移

类似dp的思想转移即可。

代码如下:

#include <iostream>
#include <cmath>
#include <vector>
#include <cstdlib>
#include <cstdio>
#include <climits>
#include <ctime>
#include <cstring>
#include <queue>
#include <stack>
#include <list>
#include <algorithm>
#include <map>
#include <set>
#define LL long long
#define Pr pair<int,int>
#define fread(ch) freopen(ch,"r",stdin)
#define fwrite(ch) freopen(ch,"w",stdout)

using namespace std;
const int INF = 0x3f3f3f3f;
const int msz = 112345;
const int mod = 1e9+7;
const double eps = 1e-8;

int a[msz],n;
int bit[msz];
map <int,int> mp2;
map <int,int> mp1;
map <int,int> tmp;

struct Query
{
    int l,r,id;
    bool operator <(const struct Query a)const
    {
        return r < a.r;
    }
};

Query que[msz];
int ans[msz];

int Lowbit(int x)
{
    return x&(-x);
}

void Add(int x,int ad)
{
    while(x <= n)
    {
        bit[x] += ad;
        x += Lowbit(x);
    }
}

int Sum(int x)
{
    int ans = 0;
    while(x)
    {
        ans += bit[x];
        x -= Lowbit(x);
    }
    return ans;
}

int main()
{
    //fread("in.in");
    //fwrite("");

    int q;

    while(~scanf("%d%d",&n,&q))
    {
        memset(bit,0,sizeof(bit));

        for(int i = 1; i <= n; ++i) scanf("%d",&a[i]);

        for(int i = 0; i < q; ++i)
        {
            scanf("%d%d",&que[i].l,&que[i].r);
            que[i].id = i;
        }

        sort(que,que+q);

        int pos = 0;
        int g;

        mp1.clear();
        mp2.clear();

        for(int i = 1; i <= n; ++i)
        {
            tmp.clear();

            for(auto iter: mp2)
            {
                g = __gcd(iter.first,a[i]);

                if(!tmp.count(g) || tmp[g] < iter.second)
                    tmp[g] = iter.second;
            }

            tmp[a[i]] = i;

            mp2.clear();
            for(auto iter: tmp)
            {
                if(!mp1.count(iter.first))
                {
                    mp1[iter.first] = iter.second;
                    Add(iter.second,1);
                }
                else if(mp1[iter.first] < iter.second)
                {
                    Add(mp1[iter.first],-1);
                    mp1[iter.first] = iter.second;
                    Add(iter.second,1);
                }

                mp2[iter.first] = iter.second;
            }

            while(pos < q && que[pos].r == i)
            {
                ans[que[pos].id] = Sum(que[pos].r)-Sum(que[pos].l-1);
                pos++;
            }
        }

        for(int i = 0; i < q; ++i)
            printf("%d\n",ans[i]);
    }

    return 0;
}

1006 Football Games(Landau’s Theorem)

题目大意:n支队伍两两比赛,赢得2分,输不得分,平局分别得1分
给出最终得分,问是否合法。

Landau’s Theorem的变形,赢得1分其余无分即为Landau’s Theorem。

具体证明没明白……判定方式就是从小到大排序。

对于此题,如果第i人赢得前面全部的,输掉后面全部的,最终所有队伍得分就会变成0 2 4 6 8 10这种情况。
那么 (1kn)ki=1s[i]>=i(i1) && ni=1s[i]==n(n1) 则分数无误。

即对于此题 得分最少的i个人的分数总和的下界 为i(i-1)

代码如下:

#include <iostream>
#include <cmath>
#include <vector>
#include <cstdlib>
#include <cstdio>
#include <climits>
#include <ctime>
#include <cstring>
#include <queue>
#include <stack>
#include <list>
#include <algorithm>
#include <map>
#include <set>
#define LL long long
#define Pr pair<int,int>
#define fread(ch) freopen(ch,"r",stdin)
#define fwrite(ch) freopen(ch,"w",stdout)

using namespace std;
const int INF = 0x3f3f3f3f;
const int msz = 10000;
const int mod = 1e9+7;
const double eps = 1e-8;
const int maxn = 233;

int num[21234];

bool judge(int n)
{
    int ans = 0;
    for(int i = 0; i < n; ++i)
    {
        ans += num[i];
        if(ans < (i+1)*i) return false;
    }

    return ans == n*(n-1);
}

int main()
{
    int t,n;

    while(~scanf("%d",&t))
    {
        while(t--)
        {
            scanf("%d",&n);
            for(int i = 0; i < n; ++i)
                scanf("%d",&num[i]);

            sort(num,num+n);

            puts(judge(n)? "T": "F");
        }
    }


    return 0;
}

1007 Friends and Enemies(二分图完美匹配)

题目大意:
m个人 n种颜色石头。
人与人之间关系要么是朋友,要么是敌人,关系不具有传递性。
每个人可以携带任何数量任何种颜色的石头(也可以不带)

对于任何两个人,如果是朋友,携带的石头至少有一种相同颜色。
如果是朋友,携带的石头颜色必须完全不同。

问n种颜色的石头能不能满足所有关系下m个人佩戴的石头都符合要求。

就是找最坏条件下m个人需要的石头种类,跟n进行比较。
把m个人分成两组,每组内部都是敌人关系,两组间两两互为朋友。
这样所需要石头种数就是两组间的连线数,并且是最坏情况。

n/2*(n+1)/2

代码如下:

#include <iostream>
#include <cmath>
#include <vector>
#include <cstdlib>
#include <cstdio>
#include <climits>
#include <ctime>
#include <cstring>
#include <queue>
#include <stack>
#include <list>
#include <algorithm>
#include <map>
#include <set>
#define LL long long
#define Pr pair<int,int>
#define fread(ch) freopen(ch,"r",stdin)
#define fwrite(ch) freopen(ch,"w",stdout)

using namespace std;
const int INF = 0x3f3f3f3f;
const int msz = 10000;
const int mod = 1e9+7;
const double eps = 1e-8;
const int maxn = 233;


int main()
{
    LL n,m;

    while(~scanf("%lld%lld",&n,&m)) puts(m >= (n/2)*((n+1)/2)? "T": "F");

    return 0;
}

1008 Function(优先队列)

题目大意:
给出n个正整数和q次询问,每次询问[l,r] 输出 a[l]moda[l+1]moda[l+2]...moda[r]

已知mod操作类似gcd操作,结果是单调的,只会小不会大。

把所有询问预存,按左边界排序,当前左边界存在于询问时,加入优先队列,对于当前位置,优先队列中大于a[i]的都对a[i]取余,取到 < a[i]即可停止,更小的肯定更无变化,根据右边界抛出即可

代码如下:

#include <iostream>
#include <cmath>
#include <vector>
#include <cstdlib>
#include <cstdio>
#include <climits>
#include <ctime>
#include <cstring>
#include <queue>
#include <stack>
#include <list>
#include <algorithm>
#include <map>
#include <set>
#define Pr pair<int,int>
#define fread(ch) freopen(ch,"r",stdin)
#define fwrite(ch) freopen(ch,"w",stdout)

using namespace std;
typedef long long LL;
const int INF = 0x3f3f3f3f;
const double eps = 1e-9;
const int maxn = 100000 + 100;

struct Query
{
    int l,r,x,id;

    bool operator >(const struct Query a)const
    {
        return x < a.x;
    }
};

int a[112345];
Query que[113245];
int ans[112345];

bool cmp(Query a,Query b)
{
    return a.l < b.l;
}

int main()
{
    int t,n,m;

    scanf("%d",&t);
    while(t--)
    {
        scanf("%d",&n);
        for(int i = 1; i <= n; ++i) scanf("%d",&a[i]);

        scanf("%d",&m);
        for(int i = 0; i < m; ++i)
        {
            scanf("%d%d",&que[i].l,&que[i].r);
            que[i].id = i;
            que[i].x = a[que[i].l];
        }

        sort(que,que+m,cmp);

        priority_queue <Query,vector<Query>,greater<Query> > q;

        int tp = 0;
        Query tmp;
        for(int i = 1; i <= n; ++i)
        {
            while(!q.empty() && q.top().x >= a[i])
            {
                tmp = q.top();
                q.pop();
                if(tmp.r < i)
                {
                    ans[tmp.id] = tmp.x;
                    continue;
                }
                tmp.x %= a[i];
                if(tmp.r == i)
                {
                    ans[tmp.id] = tmp.x;
                    continue;
                }
                q.push(tmp);
            }

            while(tp < m && que[tp].l == i)
            {
                q.push(que[tp]);
                tp++;
            }
        }

        while(!q.empty())
        {
            tmp = q.top();
            q.pop();
            ans[tmp.id] = tmp.x;
        }

        for(int i = 0; i < m; ++i)
            printf("%d\n",ans[i]);

    }

    return 0;
}

1009 Sparse Graph(补图bfs)

题目大意:
给出一个图和一个起点S(1 <= S <= n)
问补图上S到所有点的最短路(除S外)

原图边很少,用set存尚未遍历的点,bfs即可

代码如下:

#include <iostream>
#include <cmath>
#include <vector>
#include <cstdlib>
#include <cstdio>
#include <climits>
#include <ctime>
#include <cstring>
#include <queue>
#include <stack>
#include <list>
#include <algorithm>
#include <map>
#include <set>
#define LL long long
#define Pr pair<int,int>
#define fread(ch) freopen(ch,"r",stdin)
#define fwrite(ch) freopen(ch,"w",stdout)

using namespace std;
const int INF = 0x3f3f3f3f;
const int msz = 10000;
const int mod = 1e9+7;
const double eps = 1e-8;
const int maxn = 233;

set <int> s;
set <int>::iterator iter;
map <Pr,bool> mp;
int n,m;
int ans[212345];

void bfs(int u)
{
    queue <int> q;

    q.push(u);
    for(iter = s.begin(); iter != s.end(); ++iter)
        if(*iter == u)
        {
            s.erase(iter);
            break;
        }
    ans[u] = 0;

    while(!q.empty())
    {
        u = q.front();
       // printf("%d\n",u);
        q.pop();

        for(iter = s.begin(); iter != s.end(); )
        {
            int v = *iter;
            //printf("%d %d\n",u,v);
            if(!mp.count(Pr(u,v)))
            {
                q.push(v);
                ans[v] = ans[u]+1;
                iter = s.erase(iter--);
            }
            else iter++;
        }
    }
}

int main()
{
    int t,u,v;

    scanf("%d",&t);

    while(t--)
    {
        mp.clear();
        s.clear();

        scanf("%d%d",&n,&m);

        for(int i = 1; i <= n; ++i) s.insert(i);

        while(m--)
        {
            scanf("%d%d",&u,&v);
            mp[Pr(u,v)] = mp[Pr(v,u)] = 1;
        }

        scanf("%d",&u);

        memset(ans,-1,sizeof(ans));
        bfs(u);
        bool f = 0;
        for(int i = 1; i <= n; ++i)
        {
            if(i == u) continue;

            if(f) putchar(' ');
            else f = 1;

            printf("%d",ans[i]);
        }
        puts("");
    }


    return 0;
}

1010 Weak Pair(树型dp+树状数组+二分)

题目大意:n个点的树,每个点有一个价值v。
找出满足以下条件的点对。
对于点对(u,v) u为v的祖先, valuvalv<=k

树型dp过程中,每遇到一个点,把它加到树状数组里。每离开一个点,从树状数组里去掉它。

加val[u]前,二分出*val[u] <= k的最大的val
树状数组里找到当前 <= 该val的点的数量

代码如下:

#include <iostream>
#include <cmath>
#include <vector>
#include <cstdlib>
#include <cstdio>
#include <climits>
#include <ctime>
#include <cstring>
#include <queue>
#include <stack>
#include <list>
#include <algorithm>
#include <map>
#include <set>
#define LL long long
#define Pr pair<int,int>
#define fread(ch) freopen(ch,"r",stdin)
#define fwrite(ch) freopen(ch,"w",stdout)

using namespace std;
const int INF = 0x3f3f3f3f;
const int msz = 10000;
const int mod = 1e9+7;
const double eps = 1e-8;
const int maxn = 112345;

using namespace std;

struct Edge
{
    int v,next;
};

Edge eg[maxn];
int head[maxn];
int bit[maxn];
LL val[maxn];
LL to[maxn];
int in[maxn];
LL k,tp,ans;
int id;

void add(int u,int v)
{
    eg[tp].v = v;
    eg[tp].next = head[u];
    head[u] = tp++;
}

int Lowbit(int x)
{
    return x&(-x);
}

void Add(int x,int ad)
{
    while(x <= id)
    {
        bit[x] += ad;
        x += Lowbit(x);
    }
}

int Sum(int x)
{
    int ans = 0;
    while(x)
    {
        ans += bit[x];
        x -= Lowbit(x);
    }
    return ans;
}

map <int,int> mp;

LL Search(int x)
{
    int l,r;

    l = 1,r = id;
    int ans = -1;

    while(l <= r)
    {
        int mid = (l+r)>>1;

        if(to[x]*to[mid] <= k)
        {
            ans = mid;
            l = mid+1;
        }
        else r = mid-1;
    }

    if(ans == -1) return 0;
    return Sum(ans);
}

void solve(int u,int pre)
{
    int tmp = ans;
    ans += Search(mp[val[u]]);

    //printf("%d %lld %lld\n",u,val[u],ans-tmp);

    Add(mp[val[u]],1);
    for(int i = head[u]; i != -1; i = eg[i].next)
    {
        solve(eg[i].v,u);
    }
    Add(mp[val[u]],-1);
}

int main(){

    int n,t,u,v;

    scanf("%d",&t);

    while(t--)
    {
        scanf("%d%lld",&n,&k);
        for(int i = 1; i <= n; ++i)
        {
            scanf("%lld",&val[i]);
            to[i] = val[i];
        }

        sort(to+1,to+n+1);

        id = 0;
        mp.clear();
        for(int i = 1; i <= n; ++i)
        {
            if(i == 1 || to[i] != to[i-1])
            {
                id++;
                to[id] = to[i];
                mp[to[id]] = id;
            }
        }

        memset(bit,0,sizeof(bit));
        memset(head,-1,sizeof(head));
        tp = 0;

        memset(in,0,sizeof(in));
        for(int i = 1; i < n; ++i)
        {
            scanf("%d%d",&u,&v);
            in[v]++;
            add(u,v);
        }

        ans = 0;
        for(int i = 1; i <= n; ++i)
            if(!in[i])
            {
                solve(i,i);
                break;
            }

        printf("%lld\n",ans);
    }

    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值