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

来源:互联网 发布:nginx 设置访问密码 编辑:程序博客网 时间:2024/05/04 13:28

赛时只出了两题 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;}
0 0