2017 ACM-ICPC Asia Urumqi

2017 ACM-ICPC Asia Urumqi

A. Coins

最优的策略一定是:当有至少 k 枚硬币面朝下时,则选 k 枚面朝下的硬币去抛掷(任意k 枚都可以);如果不足 k 枚面朝下,则在选择所有面朝下的硬币的基础上再额外选择若干面朝上的银币。
于是有动态规划,记 d [ i ] [ j ] d[i][j] d[i][j]表示抛掷了 i i i次后,有 j j j枚硬币面朝上的概率。他们应该满足 d [ i ] [ 0 ] + d [ i ] [ 1 ] + . . . + d [ i ] [ n ] = 1 d[i][0]+d[i][1]+...+d[i][n]=1 d[i][0]+d[i][1]+...+d[i][n]=1。转移时,考虑从当前状态 ( i , j ) (i,j) (i,j)出发,抛掷的 k k k枚硬币的所有可能结果:分别有 0 0 0 k k k 枚面朝上。其中 k k k 枚硬币抛掷后有 l l l 枚面朝上的概率为 C k l / 2 k C_k^{l}/2^k Ckl/2k。时间复杂度 O ( n ∗ m ∗ k ) O(n*m*k) O(nmk)

#include<bits/stdc++.h>
using namespace std;
double d[200][200],p[200],c[200][200];
int main()
{
    c[0][0]=1;
    for(int i=1;i<=100;i++)
    {
        c[i][0]=1;
        for(int j=1;j<=i;j++)c[i][j]=c[i-1][j-1]+c[i-1][j];
    }
    p[0]=1;
    for(int i=1;i<=100;i++)p[i]=p[i-1]/2;
    int T;cin>>T;
    while(T--)
    {
        int n,m,t;
        scanf("%d%d%d",&n,&m,&t);
        memset(d,0,sizeof d);
        d[0][0]=1;
        for(int i=0;i<m;i++)
        {
            for(int j=0;j<=n;j++)
            {
                if(d[i][j]==0)continue;
                for(int k=0;k<=t;k++)
                {
                    if(n-j>=t)d[i+1][j+k]+=d[i][j]*c[t][k]*p[t];
                    else d[i+1][j-(t-(n-j))+k]+=d[i][j]*c[t][k]*p[t];
                }
            }
        }
        double ans=0;
        for(int i=1;i<=n;i++)ans+=d[m][i]*i;
        printf("%.3lf\n",ans);
    }
    return 0;
}

B. The Difference
签到题

C. The Number Triangle

假设 S [ i ] [ j ] S[i][j] S[i][j]是从 ( i , j ) (i,j) (i,j)位置向上走出来的最优路径(路径权值和最大)的最小字符串(字典序意义下)。那么对于固定的 i i i,考虑所有 S [ i ] [ j ] S[i][j] S[i][j]的字符串之间大小关系,并赋予他们排序数组 r a n k [ i ] [ j ] rank[i][j] rank[i][j]
于是我们可以利用 r a n k [ i − 1 ] [ ∗ ] rank[i-1][*] rank[i1][]去维护出来 r a n k [ i ] [ ∗ ] rank[i][*] rank[i][],因为 S [ i ] [ j ] S[i][j] S[i][j]总是对应 r [ i ] [ j ] r[i][j] r[i][j](在位置 ( i , j ) (i,j) (i,j)的字符)与某个 S [ i − 1 ] [ ∗ ] S[i-1][*] S[i1][]拼接出来的字符串,所以他们的大小关系可以利用 r a n k [ i − 1 ] [ ∗ ] rank[i-1][*] rank[i1][]计算出来。

#include<bits/stdc++.h>
using namespace std;
const int MOD=1e9+7;
const int MAX=1510;
const double PI=acos(-1.0);
typedef long long ll;
int a[MAX][MAX];
int d[MAX][MAX];
char c[MAX][MAX];
int Rank[MAX][MAX];
vector<int>v;
int n;
int cmp(const int&x,const int &y)
{
    if(Rank[n][x]!=Rank[n][y])return Rank[n][x]<Rank[n][y];
    return x<y;
}
int main()
{
    int T;
    cin>>T;
    while(T--)
    {
       cin>>n;
       for(int i=1;i<=n;i++)
       for(int j=1;j<=i;j++)
       {
           char op[3];
           scanf("%d%s",&a[i][j],op);
           c[i][j]=op[0];
       }
       Rank[1][1]=0;
       for(int i=2;i<=n;i++)
       {
           v.clear();
           for(int j=1;j<=i;j++)
           {
               d[i][j]=max(d[i-1][j-1],d[i-1][j])+a[i][j];
               Rank[i][j]=MOD;
               if(j>1&&d[i][j]==d[i-1][j-1]+a[i][j])
                Rank[i][j]=min(Rank[i][j],(c[i][j]-'a')*10000+Rank[i-1][j-1]);
               if(j<i&&d[i][j]==d[i-1][j]+a[i][j])
                Rank[i][j]=min(Rank[i][j],(c[i][j]-'a')*10000+Rank[i-1][j]);
               v.push_back(Rank[i][j]);
           }
           sort(v.begin(),v.end());
           v.erase(unique(v.begin(),v.end()),v.end());
           for(int j=1;j<=i;j++)Rank[i][j]=lower_bound(v.begin(),v.end(),Rank[i][j])-v.begin();
       }
       v.clear();
       for(int i=1;i<=n;i++)
       {
           if(d[n][i]==*max_element(d[n]+1,d[n]+n+1))v.push_back(i);
       }
       sort(v.begin(),v.end(),cmp);
       for(int i=0;i<v.size();i++)printf("%d%c",v[i],i==v.size()-1?'\n':' ');
    }
    return 0;
}

D. Fence Building

最优策略总是在选定 n n n个点后,两两连线,且满足任意三条线不交于一点。
尝试统计总的结点个数 A ( n ) A(n) A(n)与独立线段(包括圆弧上的 n n n段小弧)的总个数 B ( n ) B(n) B(n),然后利用欧拉公式就可以得到答案 A n s ( n ) = B ( n ) − A ( n ) + 1 Ans(n)=B(n)-A(n)+1 Ans(n)=B(n)A(n)+1
任意四个点,会形成一个交点,并贡献额外的 2 条独立线段。所以 A ( n ) = n + C n 4 A(n)=n+C_n^4 A(n)=n+Cn4,而 B ( n ) = n + 2 ∗ C n 4 + C n 2 B(n)=n+2*C_n^4+C_n^2 B(n)=n+2Cn4+Cn2,所以最后答案为 C n 2 + C n 4 + 1 C_n^2+C_n^4+1 Cn2+Cn4+1

E. Friends

官方题解:对于 p p p个函数记他们形成的集合 V = { 0 , 1 , . . . , p − 1 } V=\{0,1,...,p-1\} V={0,1,...,p1}。那么每一个 valuable circle of friends 形成了 V V V的一个子集,这些集合全体形成了 V V V的幂集的一个子集 E E E。问题变成找在 E E E 中同时出现次数不少于 l a m b d a = [ ( p + 1 ) / 2 ] lambda=[(p+1)/2] lambda=[(p+1)/2]的所有极大集合。

这实际上是一个经典的关联式规则学习问题。利用先验算法可以很高效的解决。

具体来说,依次找出所有大小为 1 , 2 , 3 , . . . 1, 2, 3, ... 1,2,3,...的满足在 E E E 中同时出现的次数不少于 l a m b d a lambda lambda的所有集合,依次记录为 L ( 1 ) , L ( 2 ) , L ( 3 ) , . . . L(1), L(2), L(3), ... L(1),L(2),L(3),...。用 L ( i ) L(i) L(i)去推 L ( i + 1 ) L(i+1) L(i+1):选取 L ( i ) L(i) L(i)中一个方案 U U U U U U 的大小为 i i i;向 U U U 中尝试加入新的元素 e e e,形成 W = U + { e } W=U+\{e\} W=U+{e},检查 W W W 的所有大小为 i i i 的子集是否在 L ( i ) L(i) L(i)中;对于筛选出来的 W W W,再去 E E E 中检验是否合法。
利用位运算(这里需要拆成 2 个 64 位整数来表示)可以加速判断一个集合是不是另外一个集合的子集。这便可以解决这一题。

F. Gathering Minerals

官方题解:首先注意到这一题答案不会很大,因为所有时间都是 10 10 10的倍数,实际上最大用时除以 10 10 10 大概不会超过 750 750 750。所以可以想办法在本地跑出来所有时间 t t t可以获得的最大矿物量,并提交一个带有上述计算出来的数据的程序(也就是说可以打表)。
所以说,这一题对时间效率实际上是几乎没有限制的。
再注意到一开始的资源配置是 1 1 1个基地, 4 4 4个工人, 6 6 6份供给和 50 50 50份矿物。那么就有了一个比较容易估计出来的最优策略(虽然它的正确性并不容易说清楚)。
第一阶段,以建立尽可能多的基地为唯一目标。
第二阶段,考虑制造供给,和制造新的工人为目标(注意二者的关系,供给只有在制造新的工人的时候需要,他们二者又同时需要矿物)。
第三阶段,全心全意挖矿。
相邻阶段之间的分界线,以及第二阶段的分配,需要通过搜索来考虑所有可能的细节。具体来说,对于每一个当前状态,除了需要维护此刻的基地个数,工人个数,供给量还有矿物量,还需要维护之后 12 12 12个时间点(因为所有耗时都是 10 10 10的倍数,所以这里以每 10 s 10s 10s为一个时间点)会额外得到的工人数。
对于同一时刻的两个不同的状态,可以通过一些方法去对他们进行比较,并淘汰掉一定不优秀的那些状态策略。例如,如果其中有一个状态之后 12 12 12个时间点的物资都不劣于另一个状态,则后者一定没有存在的必要,可以被淘汰掉。更强力的淘汰方法可以单独对最近的若干个时间点(比如最近的 2 2 2个时间点)做出一些相对较优的、手动构造性的策略,从而更精细的去比较和淘汰状态。精细的处理可以使这一题几乎在 1 1 1秒内搜出来所有情况。
最后注意,本题矿物的规模到达了 64 64 64位无符号整数的极限,实际上注意到他们都是偶数,可以全部除以二,从而确保中间过程的计算不会超过 64 64 64位无符号整数。

G. The Mountain

题目给出的所需求面积的图形,可以被拆分位若干个梯形(包括退化成的三角形)的并,分别求每一个梯形的面积并累加就可以了。

H. Count Numbers

F [ i ] F[i] F[i]表示数位和为 i i i的不含 0 整数有多少个,再记 G [ i ] G[i] G[i]表示数位和为 i i i的不含 0整数的和。

那么 F [ i ] = ∑ F [ i − j ] F[i] = \sum F[i-j] F[i]=F[ij] G [ i ] = ∑ ( 10 ∗ G [ i − j ] + j ∗ F [ i − j ] ) G[i] =\sum (10*G[i-j]+j*F[i-j]) G[i]=(10G[ij]+jF[ij]),其中 j j j枚举了最低位的数字,并从 1 遍历到 9。不妨同时维护相邻的 9 个位置的值(共 18 个),则可以构造18阶矩阵 A,实现它的快速转移。整个问题的答案也对应了 A 的 a b a^b ab次幂。可以利用矩阵乘法的结合律实现它的快速计算。

F[8] F[7] F[6] F[5] F[4] F[3] F[2] F[1] F[0] G[8] G[7] G[6] G[5] G[4] G[3] G[2] G[1] G[0]

伴随矩阵A:

1 1 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0
1 0 1 0 0 0 0 0 0 2 0 0 0 0 0 0 0 0
1 0 0 1 0 0 0 0 0 3 0 0 0 0 0 0 0 0
1 0 0 0 1 0 0 0 0 4 0 0 0 0 0 0 0 0
1 0 0 0 0 1 0 0 0 5 0 0 0 0 0 0 0 0
1 0 0 0 0 0 1 0 0 6 0 0 0 0 0 0 0 0
1 0 0 0 0 0 0 1 0 7 0 0 0 0 0 0 0 0
1 0 0 0 0 0 0 0 1 8 0 0 0 0 0 0 0 0
1 0 0 0 0 0 0 0 0 9 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 10 1 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 10 0 1 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 10 0 0 1 0 0 0 0 0
0 0 0 0 0 0 0 0 0 10 0 0 0 1 0 0 0 0
0 0 0 0 0 0 0 0 0 10 0 0 0 0 1 0 0 0
0 0 0 0 0 0 0 0 0 10 0 0 0 0 0 1 0 0
0 0 0 0 0 0 0 0 0 10 0 0 0 0 0 0 1 0
0 0 0 0 0 0 0 0 0 10 0 0 0 0 0 0 0 1
0 0 0 0 0 0 0 0 0 10 0 0 0 0 0 0 0 0

PS: a b a^b ab可能非常大,于是用JAVA写的。

import java.util.*;
import java.math.*;
public class Main {
	static long MOD;
	static class lenka
	{
		long a[][]=new long[20][20];
		lenka()
		{
			for(int i=0;i<18;i++)
			{
				for(int j=0;j<18;j++)a[i][j]=0;
			}
		}
	}
	static lenka cla(lenka a,lenka b)
	{
	    lenka c=new lenka();
	    for(int i=0;i<18;i++)
	    {
	        for(int j=0;j<18;j++)
	        {
	            for(int k=0;k<18;k++)
	            {
	                c.a[i][j]+=a.a[i][k]*b.a[k][j]%MOD;
	                c.a[i][j]%=MOD;
	            }
	        }
	    }
	    return c;
	}
	static long POW(BigInteger n)
	{
	    lenka a=new lenka();
	    lenka res=new lenka();
	    for(int i=0;i<18;i++)res.a[i][i]=1;
	    for(int i=0;i<9;i++)
	    {
	    	a.a[i][0]=1;
	    	a.a[i][9]=i+1;
	    }
	    for(int i=1;i<9;i++)
	    {
	    	a.a[i-1][i]=1;
	    	a.a[i+8][i+9]=1;
	   	}
	    for(int i=9;i<18;i++)a.a[i][9]=10;
	    while(n.compareTo(BigInteger.ZERO)>0)
	    {
	        if(n.mod(new BigInteger("2")).compareTo(BigInteger.ONE)==0)res=cla(a,res);
	        a=cla(a,a);
	        n=n.divide(new BigInteger("2"));
	    }
	    long d[]={128,64,32,16,8,4,2,1,1,23817625,2165227,196833,17891,1625,147,13,1,0};
	    long ans=0;
	    for(int i=0;i<18;i++)
	    {
	    	ans+=(d[i]%MOD)*res.a[i][17]%MOD;
	    	ans%=MOD;
	    }
	    return ans;
	}
	public static void main(String args[])
	{
		Scanner cin=new Scanner(System.in);
		int T=cin.nextInt();
		while((T--)>0)
		{
			BigInteger a=cin.nextBigInteger();
			int b=cin.nextInt();
			MOD=cin.nextLong();
			System.out.println(POW(a.pow(b)));
		}
	}
}

I. A Possible Tree

思路:带权并查集。r[i]表示i到p[i]路径上所有边权的异或值。

#include<bits/stdc++.h>
using namespace std;
const int MAX=1e5+10;
const int MOD=1e9+7;
const double PI=acos(-1.0);
typedef long long ll;
int p[MAX],r[MAX];
int f(int x)
{
    if(p[x]==x)return x;
    int fa=p[x];
    p[x]=f(p[x]);
    r[x]^=r[fa];
    return p[x];
}
int main()
{
    int T;
    cin>>T;
    while(T--)
    {
        int n,c;
        scanf("%d%d",&n,&c);
        for(int i=1;i<n;i++)
        {
            int x,y;
            scanf("%d%d",&x,&y);
        }
        for(int i=0;i<=n;i++)
        {
            p[i]=i;
            r[i]=0;
        }
        int ans=c+1;
        for(int i=1;i<=c;i++)
        {
            int x,y,z;
            scanf("%d%d%d",&x,&y,&z);
            if(ans!=c+1)continue;
            int fx=f(x),fy=f(y);
            if(fx==fy&&(r[x]^r[y])!=z)ans=i;
            else
            {
                p[fx]=fy;
                r[fx]=r[x]^r[y]^z;
            }
        }
        printf("%d\n",ans-1);
    }
    return 0;
}

J. Lowest Common Ancestors

不妨先以 1 1 1号点为根,记 F ( u , v ) F(u,v) F(u,v) u u u v v v在以 1 1 1为根的树内的 L C A LCA LCA
具体来说,依然考虑以 1 为根的有根树,记 T x Tx Tx是以 x x x为根的子树,并记 A n s ( x ) Ans(x) Ans(x)是以 x x x为根的情况下的答案。对于每一个结点 x x x分成三部分来计算答案:
(i)对于 u u u v v v中一个在 T x Tx Tx内,一个在 T x Tx Tx外的情况,他们在 A n s ( x ) Ans(x) Ans(x)中贡献的值为 x x x
(ii)如果有 u u u v v v同时在 T x Tx Tx内,他们在 A n s ( x ) Ans(x) Ans(x)中贡献的值就是 F ( u , v ) F(u,v) F(u,v)
(iii)如果 u u u v v v都在 T x Tx Tx外,考虑从根一路走过来他们所在的位置,如果他们同时属于根到 x x x的路径上某一个分叉内,则他们贡献的值依然是 F ( u , v ) F(u,v) F(u,v),否则考察两个分叉结点中高度较大的那一个。
先线性的求出来所有 F ( u , v ) F(u,v) F(u,v),上述的三部分可以分别利用三次 DFS 遍历来实现。在(i)和(ii)中,分别要维护子树内完全出现的询问对的 L C A LCA LCA和,以及才出现一次的询问个数。而(iii)则需要先处理高度小的结点,并将对应部分的权值从父节点的编号修改为子节点的编号。
整个算法是线性的。

PS:时限给的有点小,977ms勉强过。

#include<bits/stdc++.h>
using namespace std;
const int MOD=1e9+7;
const int MAX=1e5+10;
const double PI=acos(-1.0);
typedef long long ll;
struct EDG{int x,to,next;}ed[2*MAX];
int head[MAX],tot=0;
void addEDG(int x,int y)
{
    ed[tot].to=y;
    ed[tot].next=head[x];
    head[x]=tot++;
}
vector<int>E[MAX];
struct lenka{int x,y;}a[MAX];
int p[MAX],v[MAX],LCA[MAX],fa[MAX];
int f(int x){return p[x]==x?x:p[x]=f(p[x]);}
void dfs(int k)//利用dfs和并查集线性求LCA
{
    v[k]=1;
    for(int i=head[k];i!=-1;i=ed[i].next)
    {
        int nex=ed[i].to;
        if(nex==fa[k])continue;
        fa[nex]=k;
        dfs(nex);
        p[f(nex)]=f(k);
    }
    for(int i=E[k].size()-1;i>=0;i--)
    {
        int j=E[k][i];
        if(k==a[j].x&&v[a[j].y])LCA[j]=f(a[j].y);
        if(k==a[j].y&&v[a[j].x])LCA[j]=f(a[j].x);
    }
}
ll A[MAX],B[MAX],C[MAX];
void dfs1(int k)
{
    for(int i=head[k];i!=-1;i=ed[i].next)
    {
        int nex=ed[i].to;
        if(nex==fa[k])continue;
        dfs1(nex);
        B[k]+=B[nex]-C[nex];
    }
}
void dfs2(int k)
{
    A[k]+=A[fa[k]];
    for(int i=head[k];i!=-1;i=ed[i].next)
    {
        int nex=ed[i].to;
        if(nex==fa[k])continue;
        A[nex]+=(B[k]-B[nex]+C[nex])*k;
        dfs2(nex);
    }
}
int main()
{
    int T;
    scanf("%d",&T);
    while(T--)
    {
        int n,m;
        scanf("%d%d",&n,&m);
        assert(1<=n&&n<=100000);
        tot=0;
        for(int i=1;i<=n;i++)
        {
            E[i].clear();
            head[i]=-1;
            p[i]=i;
            v[i]=A[i]=B[i]=C[i]=0;
        }
        for(int i=1;i<n;i++)
        {
            int x,y;
            scanf("%d%d",&x,&y);
            addEDG(x,y);
            addEDG(y,x);
        }
        for(int i=1;i<=m;i++)
        {
            scanf("%d%d",&a[i].x,&a[i].y);
            E[a[i].x].push_back(i);
            E[a[i].y].push_back(i);
        }
        dfs(1);
        for(int i=1;i<=m;i++)
        {
            A[1]+=LCA[i];
            A[LCA[i]]-=LCA[i];
            B[a[i].x]++;
            B[a[i].y]++;
            B[LCA[i]]--;
            C[LCA[i]]++;
        }
        dfs1(1);
        dfs2(1);
        for(int i=1;i<=n;i++)printf("%lld%c",A[i]+B[i]*i,i==n?'\n':' ');
    }
    return 0;
}

K. Sum of the Line

考虑基于容斥原理的计算过程。如果 T T T中所有位置 T ( r , c ) = c T(r,c)=c T(r,c)=c总成立,那么答案为 1 2 + 2 2 + . . . + k 2 1^2+2^2+...+k^2 12+22+...+k2
下面考虑删去 T ( r , c ) = 0 T(r,c)=0 T(r,c)=0的位置造成的代价。
考虑若干两两不同的素数 p 1 , p 2 , . . . , p u p_1, p_2, ..., p_u p1,p2,...,pu并记 x = p 1 ∗ p 2 . . . ∗ p u x=p_1*p_2...*p_u x=p1p2...pu。则由容斥原理,上述答案需要减去 ( − 1 ) u + 1 (-1)^{u+1} (1)u+1倍的 x 2 ( 1 2 + 2 2 + . . . + [ k / x ] 2 ) x^2(1^2+2^2+...+[k/x]^2) x2(12+22+...+[k/x]2)。在 k ≤ 1 0 8 k≤10^8 k108下, u u u 不超过 9 9 9,所以只需要枚举 2 9 2^9 29种可能即可。

#include<bits/stdc++.h>
using namespace std;
const int MOD=998244353;
const int MAX= 1e4+100;
typedef long long ll;
int all=0,pr[MAX+100];        //素数表
bool isp[MAX+10];
vector<int>p;
void init()
{
	all = 0;
	memset(isp,0,sizeof isp);
	isp[1]=1;
	for(int i=2;i<=MAX;i++)
	{
		if(!isp[i])pr[all++] = i;
		for(int j=0;j<all;j++)
		{
			long long t = 1LL*pr[j]*i ;
			if(t<=MAX)
			{
				isp[t] = true;
				if(i%pr[j]==0)break;
			}
			else break;
		}
	}
	return ;
}
void div(int x) //分解成素数
{
	for(int i = 0; i < all && x > 1; i++)
	{
        if(x%pr[i]==0)p.push_back(pr[i]);
    	while( x % pr[i] == 0)x /= pr[i];
    }
	if(x > 1)p.push_back(x);
}
ll cla(ll n)//计算1^2+2^2+3^2+....+n^2
{
    ll ans=n*(n+1)/2;
    if(ans%3==0)
    {
        ans/=3;
        return (ans%MOD)*((2*n+1)%MOD)%MOD;
    }
    return (ans%MOD)*(((2*n+1)/3)%MOD)%MOD;
}
ll f(int x)
{
    ll ans=1;
    for(int i=0;i<p.size();i++,x/=2)if(x%2)ans*=p[i];
    return ans;
}
int main()
{
    init();
    int T;cin>>T;
    while(T--)
    {
        int k;
        scanf("%d",&k);
        p.clear();
        div(k);
        ll ans=cla(k);
        for(int i=1;i<(1<<(p.size()));i++)
        {
            ll a=f(i);
            if(__builtin_popcount(i)%2==0)ans+=(a*a%MOD)*cla(k/a)%MOD;//__builtin_popcount(i)计算i的二进制数中1的个数
            else ans-=(a*a%MOD)*cla(k/a)%MOD;
            ans%=MOD;
            ans=(ans+MOD)%MOD;
        }
        printf("%lld\n",ans);
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值