• 【AtCoder】AtCoder Grand Contest 015 解题报告


    点此进入比赛

    真没想到居然能自己不看题解做完一场AGC。

    不过这应该是一场比较老的比赛了,感觉相对最近的AGC来说题目难度确实算是偏低的了。

    A:A+...+B Problem(点此看题面

    • 有一个长度为(n)的序列,已知其最小值和最大值。
    • 求序列可能的和有多少种。
    • (nle10^9)

    签到题

    显然确定一个最小值和一个最大值之后,其他位置可以在([A,B])中任选,因此共有((n-2) imes(B-A)+1)种可能的和。

    但要注意(A>B)(n=1,A ot=B)两种答案为(0)的情况。

    代码:(O(1))

    #include<bits/stdc++.h>
    #define Tp template<typename Ty>
    #define Ts template<typename Ty,typename... Ar>
    #define Reg register
    #define RI Reg int
    #define Con const
    #define CI Con int&
    #define I inline
    #define W while
    using namespace std;
    int n,A,B;
    int main()
    {
    	if(scanf("%d%d%d",&n,&A,&B),A>B||(n==1&&A^B)) return puts("0"),0;//判无解
    	return printf("%lld
    ",1LL*(n-2)*(B-A)+1),0;//直接计算答案
    }
    

    B:Evilator(点此看题面

    • 一个(n)层的楼,从第(i)层出发的电梯可以向给定方向(s_i)UD)一次运行任意层。
    • (f(i,j))表示从第(i)层到第(j)层至少需要坐几次电梯,求(sum_{i=1}^nsum_{j=1}^nf(i,j))
    • (nle10^5),保证(s_1)U(s_n)D

    签到题

    显然,从第(i)层楼往上到任意层,如果(s_i)U则需要一次,为D则需要两次。往下的情况与之相反。

    那么只要枚举从哪层楼出发直接做即可。

    代码:(O(n))

    #include<bits/stdc++.h>
    #define Tp template<typename Ty>
    #define Ts template<typename Ty,typename... Ar>
    #define Reg register
    #define RI Reg int
    #define Con const
    #define CI Con int&
    #define I inline
    #define W while
    #define N 100000
    using namespace std;
    int n;char s[N+5];
    int main()
    {
    	scanf("%s",s+1),n=strlen(s+1);
    	RI i;long long t=0;for(i=1;i<=n;++i) t+=n-1+(s[i]^'U'?n-i:i-1);//枚举从第i层楼出发
    	return printf("%lld
    ",t),0;
    }
    

    C:Nuske vs Phantom Thnook(点此看题面

    • 给定一个(n imes m)(01)矩阵,满足所有(1)构成的连通块是森林。
    • (q)组询问,每次求一个子矩阵中(1)构成的连通块数。
    • (n,mle2 imes10^3,qle2 imes10^5)

    套路题

    众所周知,森林满足一个性质:连通块数=点数-边数。

    那么我们只要知道询问的子矩阵中的点数和边数,就可以计算出连通块数了。

    而这只要二维前缀和差分一下就行了。

    代码:(O(nm+q))

    #include<bits/stdc++.h>
    #define Tp template<typename Ty>
    #define Ts template<typename Ty,typename... Ar>
    #define Reg register
    #define RI Reg int
    #define Con const
    #define CI Con int&
    #define I inline
    #define W while
    #define N 2000
    using namespace std;
    int n,m,Qt,d[N+5][N+5],h[N+5][N+5],l[N+5][N+5];char a[N+5][N+5];
    int main()
    {
    	RI i,j;for(scanf("%d%d%d",&n,&m,&Qt),i=1;i<=n;++i) scanf("%s",a[i]+1);
    	for(i=1;i<=n;++i) for(j=1;j<=m;++j)
    		d[i][j]=d[i-1][j]+d[i][j-1]-d[i-1][j-1]+(a[i][j]&1),//点
    		h[i][j]=h[i-1][j]+h[i][j-1]-h[i-1][j-1]+(a[i][j]&a[i-1][j]&1),//行之间的边
    		l[i][j]=l[i-1][j]+l[i][j-1]-l[i-1][j-1]+(a[i][j]&a[i][j-1]&1);//列之间的边
    	RI xx,yx,xy,yy,t;W(Qt--) scanf("%d%d%d%d",&xx,&yx,&xy,&yy),
    		t=d[xy][yy]-d[xx-1][yy]-d[xy][yx-1]+d[xx-1][yx-1],
    		t-=h[xy][yy]-h[xx][yy]-h[xy][yx-1]+h[xx][yx-1],
    		t-=l[xy][yy]-l[xx-1][yy]-l[xy][yx]+l[xx-1][yx],printf("%d
    ",t);return 0;//连通块数=点数-边数
    }
    

    D:A or...or B Problem(点此看题面

    • 给定(A,B),你可以在([A,B])中选任意个数按位或起来,求可能的结果数。
    • (Ale B<2^{60})

    初步思路

    首先,若(A=B),显然答案就是(1)

    否则,发现(A)(B)中相同的前缀无论如何都不可能改变,我们索性把它们全删掉。

    现在(A)(B)的最高位(设其为第(i)位)是不同的,且显然(A)的最高位为(0)(B)的最高位为(1)

    然后我们考虑把可能的结果分成两类:第(i)位为(0)和第(i)位为(1)

    (i)位为(0)

    此时就相当于([2^i,B])中的数都不能选择, 那么就是([A,2^i))中选择的方案数

    然后,([A,2^i))显然都是可以作为答案的,而若在此基础上或上任何一个数,都不可能使值变小,也不可能使值超出(2^i-1),因此(2^i-A)就是这种情况下的答案

    (i)位为(1)

    方便起见,先除去(B)中的第(i),就变成在([A,2^i))([0,B])中选择。

    然后考虑找到(B)中此时最高的(1)所在的位置(设其为第(j)位,若没有则(j=-1))。

    显然(Bge 2^j),那么(2^{j-1},2^{j-2},...,2^0)肯定也都在([0,B])的范围中。

    也就是说,仅在([0,B])中选择,我们可以得到的值除去第(i)位之后的范围是([0,2^{j+1}))

    那么现在就变成了在([0,2^{j+1}))([A,2^i))两个区间中任选数。

    一种情况是(Ale2^{j+1}),那么([0,2^i))中的所有数都能得到,因此(2^i)就是这种情况下的答案

    另一种情况是(A>2^{j+1}),对于([0,2^{j+1}))中的数已经达到了它们的极限,而([A,2^i))其实和前面第(i)位为(0)的情况一样,因此(2^{j+1}+(2^i-A))就是这种情况下的答案

    代码实现真的非常简洁。

    代码:(O(logB))

    #include<bits/stdc++.h>
    #define Tp template<typename Ty>
    #define Ts template<typename Ty,typename... Ar>
    #define Reg register
    #define RI Reg int
    #define Con const
    #define CI Con int&
    #define I inline
    #define W while
    #define LL long long
    using namespace std;
    int main()
    {
    	LL A,B;if(scanf("%lld%lld",&A,&B),A==B) return puts("1"),0;//如果A=B
    	RI i;for(i=60;!((A^B)>>i&1);--i) A>>i&1&&(A^=1LL<<i,B^=1LL<<i);LL t=(1LL<<i)-A;//消去相同前缀,t记录第i位为0的答案
    	RI j=i-1;W(~j&&!(B>>j&1)) --j;if(A<=(1LL<<j+1)) return printf("%lld
    ",t+(1LL<<i)),0;//找到此时最高位j,若能全取到
    	return printf("%lld
    ",(t<<1)+(1LL<<j+1)),0;//若不能全取到
    }
    

    E:Mr.Aoki Incubator(点此看题面

    • 数轴上有(n)个点,每个点有一个初始位置(p_i)以及一个固定的向正方向移动的速度(v_i)
    • 初始时你可以选中若干点染色,一个染色点会给所有碰到的点都染上色。
    • 求有多少种方案,会使得最终所有点都被染上了色。
    • (nle2 imes10^5)

    按初始位置排序

    我们先把所有点按初始位置排个序,再作思考。

    一个非常基础也非常重要的问题,就是一个点不光可能被直接染色,也可能被间接染色(就是一个未被选中的点经过了某个被选中的点,再经过了当前点)。

    先只考虑一个点(x)被它后面的点(y)染色的情况,就按上面的说法分成两种:

    • 直接染色:要求满足(v_x<v_y)
    • 间接染色:设原先被选中染色的点为(z)。则(v_z<v_x<v_y,p_z>p_y)

    然后发现,不管如何,都要满足(v_x<v_y)

    再仔细一想,发现只要存在一个满足(v_x<v_y)(y),使得([p_y,p_x])之中存在一个点被染过色,那么(x)就会被染色。

    最后结合(x)被它前面的点染色的情况,总结一下,就是要找到最小的满足(v_x<v_l)的点,以及最大的满足(v_x>v_r)的点,只要([l,r])中有一个点被染过色,那么(x)就会被染色。

    至于如何求出这个区间,直接维护一个栈,然后在上面二分就可以了。

    动态规划

    现在就变成了这样一个问题:存在若干个区间,要求每个区间中至少被选中一个点,求方案数。

    (f_i)表示最后一个点选中(i)的方案数。

    假设要转移到(f_j),显然,因为每个区间中都至少被选中一个点,所以([i+1,j-1])之间不能存在一个完整的限制区间

    因此我们找到所有左端点大于(i)的区间,那么(j)不能大于它们右端点的最小值(设其为(Mx_i))。

    也就是说(f_i)可以转移到([i+1,Mx_i]),也就是给这段区间的(f_j)都加上(f_i)

    直接差分一下就好了。

    代码:(O(nlogn))

    #include<bits/stdc++.h>
    #define Tp template<typename Ty>
    #define Ts template<typename Ty,typename... Ar>
    #define Reg register
    #define RI Reg int
    #define Con const
    #define CI Con int&
    #define I inline
    #define W while
    #define N 200000
    #define X 1000000007
    #define Inc(x,y) ((x+=(y))>=X&&(x-=X))
    using namespace std;
    int n,S[N+5],f[N+5],g[N+5],Mx[N+5];
    struct line {int k,b;I bool operator < (Con line& o) Con {return b<o.b;}}s[N+5];
    struct P {int l,r;I bool operator < (Con P& o) Con {return l<o.l;}}p[N+5];
    I int F(RI l,RI r,CI x) {RI mid;W(l^r) s[S[mid=l+r>>1]].k>=x?r=mid:l=mid+1;return r;}//在栈中[l,r]上二分,求出第一个大于等于x的位置
    int main()
    {
    	RI i,x,y;for(scanf("%d",&n),i=1;i<=n;++i) scanf("%d%d",&s[i].b,&s[i].k);sort(s+1,s+n+1);
    	RI T=0;for(i=1;i<=n;++i)//从前往后
    		p[i].l=s[S[T]].k<=s[i].k?i:S[F(1,T,s[i].k+1)],s[S[T]].k<s[i].k&&(S[++T]=i);
    	for(s[0].k=2e9,T=n+1,i=n;i;--i)//从后往前
    		p[i].r=s[S[T]].k>=s[i].k?i:S[F(T,n+1,s[i].k)-1],s[S[T]].k>s[i].k&&(S[--T]=i);
    	RI j,t=n+1;for(sort(p+1,p+n+1),i=j=n;~i;Mx[i--]=t) W(p[j].l>i) t=min(t,p[j--].r);//倒枚一遍预处理Mx[i]
    	for(f[0]=1,i=0;i<=n+1;++i) i&&Inc(g[i],g[i-1]),
    		Inc(f[i],g[i]),Inc(g[i+1],f[i]),Inc(g[Mx[i]+1],X-f[i]);//差分打标记转移
    	return printf("%d
    ",f[n+1]),0;//输出答案
    }
    

    F:Kenus the Ancient Greek(点此看题面

    • 定义(F(i,j))表示求(gcd(i,j))时的辗转相除次数。
    • 对于所有(iin[1,A],jin[1,B]),求(F(i,j))的最大值以及能取到最大值的(i,j)组数。
    • 数据组数(le3 imes10^5,A,Ble10^{18})

    斐波那契数

    (A=B)时,显然我们可以通过斐波那契数来卡到(F(i,j))的上界。

    实际上,当(Ale B)时,我们依然可以利用斐波那契数,只要找到最大的(t)满足(Fib(t)le A,Fib(t+1)le B),那么答案就是(t)

    因此第一问其实是很好做的,于是主要就是要考虑第二问,即方案数。

    大胆猜结论

    首先我们先强制最开始的两个数(a,b)满足(a<b<2a),则这种情况实际对应的方案数就是(lfloorfrac{B-b}a floor+1)

    然后考虑斐波那契递推式(Fib(i)=Fib(i-1)+Fib(i-2)),那么我们所能做的就是对于若干(Fib(i))修改一下递推式的系数得到(Fib'(i)=kFib'(i-1)+Fib'(i-2))

    接着我们发现,(a)必须在([Fib(t),A])范围内,因此它的上界是卡得非常紧的。

    具体地,我们发现,对于一个表达式,如果我们令(k>2)了,由于(2Fib(i-1)>Fib(i)),那么最后的值至少翻倍,必然超过(Fib(t+1)),更超过(A)

    然后我们考虑(frac{Fib(i)}{Fib(i+1)}≈0.618),如果修改的表达式个数(>2),由于(1.618^2>2),那么最后的值也至少翻倍,必然超过(Fib(t+1)),更超过(A)

    所以,得出结论,最多只会修改一个表达式,且只会把某一个(k)改成(2)

    剩下的就很简单了。

    代码:(O(88T))

    #include<bits/stdc++.h>
    #define Tp template<typename Ty>
    #define Ts template<typename Ty,typename... Ar>
    #define Reg register
    #define RI Reg int
    #define Con const
    #define CI Con int&
    #define I inline
    #define W while
    #define LL long long
    #define X 1000000007
    using namespace std;
    int t;LL A,B,Fib[88],f[88][2];
    I int Solve(LL A,LL B)//求答案
    {
    	#define Calc(a,b) (B>=b?((B-b)/a+1)%X:0)//计算在[b,B]中与b模a同余的数的个数
    	RI i,ans=Calc(Fib[t],Fib[t+1]);LL a,b;for(i=1;i^t;++i)
    		a=f[t-i][0]*Fib[i]+f[t-i][1]*(Fib[i-1]+2*Fib[i]),//计算a
    		b=f[t-i+1][0]*Fib[i]+f[t-i+1][1]*(Fib[i-1]+2*Fib[i]),//计算b
    		a<=A&&b<=B&&(ans=(ans+Calc(a,b))%X);
    	return ans;
    }
    int main()
    {
    	RI Tt,i;for(Fib[0]=Fib[1]=f[0][0]=f[1][1]=1,i=2;i^88;++i)
    		Fib[i]=Fib[i-2]+Fib[i-1],f[i][0]=f[i-2][0]+f[i-1][0],f[i][1]=f[i-2][1]+f[i-1][1];//预处理斐波那契数以及每一项中Fib(0)和Fib(1)的贡献
    	scanf("%d",&Tt);W(Tt--)
    	{
    		scanf("%lld%lld",&A,&B),A>B&&(swap(A,B),0);//强制A≤B
    		if(A==1||B<=2) {printf("1 %d
    ",A*B%X);continue;}//特判小情况
    		t=upper_bound(Fib+1,Fib+88,A)-Fib-1,Fib[t+1]>B&&--t,//求出t
    		printf("%d %d
    ",t,(Solve(A,B)+Solve(A,A))%X);
    	}return 0;
    }
    
  • 相关阅读:
    智能家居测试思路
    Linux中,&和&&,|和||
    Eclipse设置保存代码时自动格式化代码
    Eclipse常用快捷键
    Eclipse设置快捷出现函数
    Eclipse设置代码背景色
    PDF转换成Word后乱码怎么办?
    Able2Extract快捷键汇总整理
    安装并激活Parallels Desktop商业版
    想在Mac上使用CAD?
  • 原文地址:https://www.cnblogs.com/chenxiaoran666/p/AtCoderAGC015.html
Copyright © 2020-2023  润新知