1. 程式人生 > 實用技巧 >AtCoder Grand Contest 015

AtCoder Grand Contest 015

Preface

STO CXR ORZ爆切所有題,同時成功完成了所有題目一遍A的record(感謝陳指導試水)

不過qs這場對比起最近的幾場難度還是比較低的,但F感覺還是因為陳指導的神仙氣質才能在迷霧中照亮前路


A - A+...+B Problem

強制\(A,B\)兩個數都要選,那麼剩下的數能構成的範圍就是\([(n-2)\times A,(n-2)\times B]\)

注意特判\(n=1\)\(A>B\)的情況

#include<cstdio>
#define RI register int
#define CI const int&
int n,a,b;
int main()
{
	scanf("%d%d%d",&n,&a,&b); if (a>b) return puts("0"),0;
	if (n==1) return puts(a==b?"1":"0"),0;
	return printf("%lld\n",1LL*(n-2)*b-1LL*(n-2)*a+1),0;
}

B - Evilator

因為\(S_1\)一定是U\(S_n\)一定是D,因此對於某個位置,只要根據它的按鈕就可以確定它哪個方向是一步到位,另一個方向是兩步的

#include<cstdio>
#include<cstring>
#define RI register int
#define CI const int&
const int N=100005;
int n; char s[N]; long long ans;
int main()
{
	scanf("%s",s+1); n=strlen(s+1); for (RI i=1;i<=n;++i)
	if (s[i]=='U') ans+=2*(i-1)+n-i; else ans+=i-1+2*(n-i);
	return printf("%lld",ans),0;
}

C - Nuske vs Phantom Thnook

經典老題,直接拷之前寫的程式碼了

眾所周知無環的聯通塊個數=點數-邊數,分別用二維字首和維護這兩個東西即可

複雜度\(O(nm+q)\)

#include<cstdio>
#include<cctype>
#define RI register int
#define CI const int&
using namespace std;
const int N=2005;
int n,m,q,a[N][N],pt[N][N],eg[N][N],h[N][N],l[N][N],x1,x2,y1,y2; char ch;
inline void get_digit(char& ch)
{
	while (!isdigit(ch=getchar()));
}
inline int getpt(CI x1,CI y1,CI x2,CI y2)
{
	return pt[x2][y2]-pt[x1-1][y2]-pt[x2][y1-1]+pt[x1-1][y1-1];
}
inline int geth(CI x,CI y1,CI y2)
{
	return h[x][y2]-h[x][y1-1];
}
inline int getl(CI y,CI x1,CI x2)
{
	return l[x2][y]-l[x1-1][y];
}
inline int geteg(CI x1,CI y1,CI x2,CI y2)
{
	return eg[x2][y2]-eg[x1-1][y2]-eg[x2][y1-1]+eg[x1-1][y1-1]-geth(x1-1,y1,y2)-getl(y1-1,x1,x2);
}
int main()
{
	RI i,j; for (scanf("%d%d%d",&n,&m,&q),i=1;i<=n;++i)
	for (j=1;j<=m;++j) get_digit(ch),a[i][j]=ch&15;
	for (i=1;i<=n;++i) for (j=1;j<=m;++j)
	pt[i][j]=pt[i][j-1]+pt[i-1][j]-pt[i-1][j-1]+a[i][j];
	for (i=1;i<n;++i) for (j=1;j<=m;++j)
	h[i][j]=h[i][j-1]+(a[i][j]==1&&(a[i][j]==a[i+1][j]));
	for (j=1;j<m;++j) for (i=1;i<=n;++i)
	l[i][j]=l[i-1][j]+(a[i][j]==1&&(a[i][j]==a[i][j+1]));
	for (i=1;i<=n;++i) for (j=1;j<=m;++j)
	{
		eg[i][j]=eg[i-1][j]+eg[i][j-1]-eg[i-1][j-1];
		if (a[i][j]) eg[i][j]+=(i!=1?(a[i][j]==a[i-1][j]):0)+(j!=1?(a[i][j]==a[i][j-1]):0);
	}
	for (i=1;i<=q;++i)
	{
		scanf("%d%d%d%d",&x1,&y1,&x2,&y2);
		printf("%d\n",getpt(x1,y1,x2,y2)-geteg(x1,y1,x2,y2));
	}
	return 0;
}

D - A or...or B Problem

首先我們發現\(A,B\)二進位制下相同的那一段字首是無用的,顯然可以刪去

接下來我們設最高的不同位為\(i\),容易發現此時一定是\(B\)這一位上為\(1\)\(A\)這一位上為\(1\)

此時我們若

第i位為0

這種方案就相當於\([2^i,B]\)中的數都不能選,答案就是在\([A,2^i)\)中選擇的方案數

顯然\([A,2^i)\)中的每一個數都可以作為答案,而且顯然做或操作也不能讓這個值超出\(2^i\)的範圍,因此這部分的答案就是\(2^i-A\)

第i位為1

其實強制\(B\)的第\(i\)位必須選,我們把這一位拿掉就相當於後面的在\([A,2^i)\)\([0,B]\)中選擇

找到\(B\)中此時最高位的\(1\)所在的位置,此時顯然\(2^0,2^1\cdots,2^{j}\)都在\([0,B]\)中,因此我們可以構造出\([0,2^{j+1})\)的數

因此現在的問題就變為了在\([0,2^{j+1})\)\([A,2^i)\)中任選,我們再討論一下:

  • \(A\le 2^{j+1}\),此時兩個區間的並就達到了答案的上界\([0,2^i)\)了,因此此時答案就是\(2^i\)
  • \(A>2^{j+1}\),還是利用或的性質,只會使一個數變大但無法超出這個區間的限制,因此此時答案就是\(2^i-A+2^{j+1}\)

因此這道題就做完了,複雜度\(O(\log B)\)

#include<cstdio>
#define RI register int
#define CI const int&
#define int long long
using namespace std;
int a,b,n;
signed main()
{
	RI i,j; scanf("%lld%lld",&a,&b); if (a==b) return puts("1"),0;
	for (i=60;~i;--i) if (((a>>i)&1)!=((b>>i)&1)) break;
	a&=(1LL<<i)-1; b&=(1LL<<i)-1; int tp=(1LL<<i)-a; 
	for (j=i-1;~j;--j) if ((b>>j)&1) break;
	if (a<=(1LL<<j+1)) printf("%lld",(1LL<<i)+tp);
	else printf("%lld",2LL*tp+(1LL<<j+1)); return 0;
}

E - Mr.Aoki Incubator

首先我們可以把每個人看作一條射線,斜率就是速度,截距就是初始位置,它們與\(y\)軸的交點就是射線的端點

粗略地一想我們以為一條射線\(y=k_ix+b_i\)\(y=k_jx+b_j\)能染色到對方的條件是它們在一四象限有交點,但是後來發現可以間接地染色

我們分析後發現對於某條射線\(y=k_jx+b_j\)能染到\(y=k_ix+b_i\)的條件僅僅是\(k_j>k_i,b_j<b_i\)\(k_j<k_i,b_j>b_i\),此時\(j\)還可以把它和\(i\)之間的其它直線都染了

容易發現如果我們把所有射線按\(b_i\)排序後,我們對於每條直線\(i\)找出\(l_i=\min _{k_j>k_i} b_j\)\(r_i=\max _{k_j<k_i} b_j\),那麼\([l_i,r_i]\)中的任意一條直線被染色都可以把\(i\)染色

考慮如何求出\([l_i,r_i]\),顯然我們可以在掃描的時候維護一個元素遞增的棧,在上面二分即可

那麼現在問題就轉化為有\(n\)個區間,現在要選擇某些點使得每個區間中都要有至少一個點

\(f_i\)表示上一個以\(i\)為結尾的方案數,考慮\(f_i\)能轉移到\(f_j\)的充要條件是\((i,j)\)中不存在一個完整的區間

因此我們考慮對於某個\(i\)它能轉移到的最遠點(不能取)就是所有左端點大於它的區間中右端點的最小值,顯然我們可以把所有區間按\(l_i\)排序後找出這個範圍

然後就可以直接DP了,這裡由於區間修改單點查詢且有嚴格的前後順序因此可以直接差分處理,總複雜度\(O(n\log n)\)

#include<cstdio>
#include<algorithm>
#include<iostream>
#define RI register int
#define CI const int&
using namespace std;
const int N=200005,mod=1e9+7;
struct line
{
	int k,b;
	friend inline bool operator < (const line& A,const line& B)
	{
		return A.b<B.b;
	}
}l[N];
struct interval
{
	int l,r;
	friend inline bool operator < (const interval& A,const interval& B)
	{
		return A.l<B.l;
	}
}a[N]; int n,stk[N],top,f[N],cr[N],cur,mr;
inline void inc(int& x,CI y) { if ((x+=y)>=mod) x-=mod; }
inline void dec(int& x,CI y) { if ((x-=y)<0) x+=mod; }
inline int find1(CI x)
{
	int L=1,R=top,mid,ret; while (L<=R)
	if (l[stk[mid=L+R>>1]].k>=x) ret=mid,R=mid-1; else L=mid+1; return ret;
}
inline int find2(CI x)
{
	int L=1,R=top,mid,ret; while (L<=R)
	if (l[stk[mid=L+R>>1]].k<=x) ret=mid,R=mid-1; else L=mid+1; return ret;
}
int main()
{
	RI i,j; for (scanf("%d",&n),i=1;i<=n;++i) scanf("%d%d",&l[i].b,&l[i].k);
	for (sort(l+1,l+n+1),i=1;i<=n;++i)
	{
		if (!top||l[stk[top]].k<l[i].k) stk[++top]=i; a[i].l=stk[find1(l[i].k)];
	}
	for (top=0,i=n;i;--i)
	{
		if (!top||l[stk[top]].k>l[i].k) stk[++top]=i; a[i].r=stk[find2(l[i].k)];
	}
	for (sort(a+1,a+n+1),mr=n+1,i=j=n;~i;--i)
	{
		while (j&&a[j].l==i+1) mr=min(mr,a[j--].r); cr[i]=mr;
	}
	for (f[0]=1,f[1]=mod-1,i=0;i<=n;++i)
	inc(cur,f[i]),inc(f[i+1],cur),dec(f[cr[i]+1],cur);
	return inc(cur,f[n+1]),printf("%d",cur),0;
}

F - Kenus the Ancient Greek

第一問的處理

首先我們考慮到如果\(A=B\)的話那麼我們可以利用經典結論,消耗輾轉相除次數最多的情況就是一對相鄰的斐波那契數

稍加推廣我們發現這個結論對於\(A<B\)的情況仍然成立,具體地,我們找到\(\le A\)的最大的\(Fib_i\),若\(Fib_{i+1}\le B\)那麼\(i\)就是答案,否則答案就是\(i-1\)

第二問的處理

我們考慮統計個數,非常直觀地我們設想到相同步數的不同構造肯定可以通過在某些步數時修改下遞推係數,即\(Fib_i=Fib_{i-2}+k\times Fib_{i-1}\)

感性地,我們感覺出每次變化的係數不能很大,並且要修改的位置不是很多,接下來我們來證明兩個結論

  • \(k\le 2\):由於\(Fib_i\le 2Fib_{i-1}\),若\(k>2\)則某個值必然至少翻倍。而此時最後的值也必然至少翻倍,必然超過了上界\(A\)。但是注意要去除最後一步的情況,因為此時若\(A,B\)相差懸殊可以導致\(k\)取到很大
  • 要修改的位置至多為一個(除了最後一步):考慮\(\lim_{i\to \infty}\frac{Fib_{i+1}}{Fib_i}=\frac{\sqrt 5+1}{2}\),我們手動打個表發現當\(n=10\)左右這個值就在\(1.618\)左右了。我們考慮我們每次將一個\(k\)改為\(2\)就會導致\(Fib_i\)乘上\(\frac{\sqrt 5+1}{2}\),而\((\frac{\sqrt 5+1}{2})^2>2\),因此修改的地方不可能超過一處

因此我們就可以直接列舉在哪個位置修改\(k\)來算答案了,考慮快速算出修改後的值可以通過記錄係數的方法來處理

由於\(10^{18}\)以內的斐波那契數大約有\(88\)個,因此總複雜度即為\(O(88\times Q)\)

#include<cstdio>
#include<algorithm>
#define RI register int
#define CI const int&
#define int long long
using namespace std;
const int N=88,mod=1e9+7;
int q,t,A,B,fib[N],f[N][2];
#define calc(a,b) (B>=b?((B-b)/a+1)%mod:0)
inline int solve(CI A,CI B)
{
	int ret=calc(fib[t],fib[t+1]); for (RI i=1;i<t;++i)
	{
		int a=f[t-i][0]*fib[i]+f[t-i][1]*(fib[i-1]+2LL*fib[i]),
		b=f[t-i+1][0]*fib[i]+f[t-i+1][1]*(fib[i-1]+2LL*fib[i]);
		if (a<=A&&b<=B) (ret+=calc(a,b))%=mod;
	}
	return ret;
}
signed main()
{
	RI i; for (fib[0]=fib[1]=f[0][0]=f[1][1]=1,i=2;i<N;++i)
	fib[i]=fib[i-1]+fib[i-2],f[i][0]=f[i-1][0]+f[i-2][0],f[i][1]=f[i-1][1]+f[i-2][1];
	for (scanf("%lld",&q),i=1;i<=q;++i)
	{
		scanf("%lld%lld",&A,&B); if (A>B) swap(A,B);
		if (A==1) { printf("1 %lld\n",B%mod); continue; }
		if (B<=2) { printf("1 %lld\n",A*B%mod); continue; }
		t=upper_bound(fib+1,fib+N,A)-fib-1; if (fib[t+1]>B) --t;
		printf("%lld %lld\n",t,(solve(A,B)+solve(A,A))%mod);
	}
	return 0;
}

Postscript

所以說陳指導時我們的紅太陽