1. 程式人生 > 實用技巧 >【被虐】第三場選拔賽補題

【被虐】第三場選拔賽補題

補題時間。。。

這回出題人出的都是mbg風格的題。。。話不多說,挑幾個有意思的說說。

B題,題意給定\(n\leqslant 10^9\),求

\[\sum_{p\leqslant n}\frac 1p,\ (p\ is\ prime) \]

的值並四捨五入取整。

顯然直接求是不行的,因為會超時,事實上這題我目前還沒發現有什麼低於線性的演算法,所以這題差不多是個結論題。結論是,在\(10^9\)的範圍內,該和式的值取整後不會超過3,所以只要二分找出幾個值的邊界即可。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=6e6+5;
int prime[N],cnt;
bool vis[N];
int main()
{
	int T;
	scanf("%d",&T);
	while(T--)
	{
		int n;
		scanf("%d",&n);
		if(n<3) printf("0\n");
		else if(n<29) printf("1\n");
		else if(n<11789) printf("2\n");
		else printf("3\n");
	}
	return 0;
}

F題:

一眼看上去很牛逼,其實很沙比。。。就模擬就行了。。。

但是這題正好觸及到了我的知識盲區(動態空間陣列),只要會vector就行了(或手寫連結串列)。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef double db;
#define i8 __int128
#define rep(i,l,r) for(int i=l;i<=r;i++)
#define vrep(i,__v) for(int i=0;i<__v.size();i++)
inline i8 read()
{
    i8 ret=0,sgn=1;
    char c=getchar();
    while(c<'0'||c>'9')
    {if(c=='-') sgn=-1;c=getchar();}
    while(c>='0'&&c<='9')
    {ret=ret*10+c-'0';c=getchar();}
    return ret*sgn;
}
void write(i8 x)
{
    if(x<0) x=-x,putchar('-');
    if(x>9) write(x/10);
    putchar(x%10+'0');
}
const int N=1e6+5;
vector<int> v[N];
int main()
{
   int n,x;
   scanf("%d",&n);
   rep(i,1,n)
   {
       scanf("%d",&x);
       v[x].push_back(i);
   }
   int ans=2e9;
   rep(i,1,n)
   {
       scanf("%d",&x);
       vrep(j,v[x])
       {
           ans=min(ans,abs(v[x][j]-i));
       }
   }
   if(ans==2e9) printf("-1\n");
   else printf("%d\n",ans);
   return 0;
}

H題:給一顆有根樹,樹節點有權值,有兩種操作:1.單點修改,將一個節點的權值+v。2.查詢,求某節點到樹根的簡單路徑上所有點權平方和,答案對\(2^{64}\)取模。

這個題考試的時候想到了樹鏈剖分 ,但是死活調不出來,自己以為是樹鏈剖分寫次了,結果沒想到是一個前所未見的玄學錯誤。。。我對模數賦值的時候直接賦值成了\(2^{64}\)。。。然後成功RE。。。(事實上這個數在計算機里根本不存在。。。),這也側面證明了樹剖沒錯。。。哭了,然而自己賽後糾錯後還是T了(樹剖複雜度真高)。事實上,存在一種很簡單的演算法,僅僅需要搞出樹的dfs序即可。我們維護一個類似字首和的東西,維護每個點到根節點路徑的答案,在dfs的時候可以直接求出來,考慮一次單點修改只對某節點及其子樹的答案有影響,我們再維護一個樹狀陣列,差分修改,然後求的時候直接查詢樹狀陣列就好了。(其實很簡單)

(小技巧:取模\(2^{64}\)可以用unsigned long long儲存資料,自然溢位就可以了。然而我用的int128也不是不可以。。。)

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;//simplify long long
typedef unsigned long long ull;
typedef double db;
typedef long double ldb;
#define inf 0x3f3f3f3f
#define pi 3.14159265358979
#define rep(i, l, r) for(int i = l; i <= r; i ++)
#define lop(i, r, l) for(int i = r; i >= l; i --)
#define step(i, l, r, __step) for(int i = l; i <= r; i += __step)
#define revp(i, r, l, __step) for(int i = r; i >= l; i -= __step)
#define reg regsiter
#define RI regsiter int
#define RL regsiter long long
#define rerep(i, l, r) for(regsiter int i = l; i <= r; i ++)
#define relop(i, r, l) for(regsiter int i = r; i >= l; i --)
#define i8 __int128
//#define __int128 ll//don't forget delete it in contest!
inline i8 read()//fast read
{
	i8 ret = 0, sgn = 1;
	char chr = getchar();
	while(chr < '0' || chr > '9')
	{if(chr == '-') sgn = -1; chr = getchar();}
	while(chr >= '0' && chr <= '9')
	{ret = ret * 10 + chr - '0'; chr = getchar();}
	return ret * sgn;
}
void write(i8 x)//int128's output
{
	if(x < 0) putchar('-'), x = -x;
	if(x > 9) write(x / 10);
	putchar(x % 10 + '0');
}
const int N = 1e6 + 5;
const int M = 2e5 + 5;
int n,m,S;
struct edge{
	int to,nxt;
}e[N<<1];
int head[N],tot;
void adde(int f,int t)
{
	e[++tot]=(edge){t,head[f]};
	head[f]=tot;
}
int deep[N],fa[N],siz[N];
int inseg[N],intr[N],scnt=0;
i8 sum[N],num[N],mod=(1ll<<63);
void dfs(int u,int f)
{
	deep[u]=deep[f]+1;
	sum[u]=sum[f]+num[u]*num[u];
	fa[u]=f;
	siz[u]=1;
	inseg[u]=++scnt;
	intr[scnt]=u;
	for(int i=head[u];i;i=e[i].nxt)
	{
		int v=e[i].to;
		if(v==f) continue;
		dfs(v,u);
		siz[u]+=siz[v];
	}
}
i8 atr[N];
#define lowbit(x) x&-x
void modify(int x,i8 v)
{
	while(x<=n)
	{
		atr[x]+=v,atr[x]%=mod;
		x+=lowbit(x);
	}
}
i8 ask(int x)
{
	i8 ret=0;
	while(x)
	{
		ret+=atr[x],ret%=mod;
		x-=lowbit(x);
	}
	return ret;
}
int main()
{
	mod*=2;
	scanf("%d%d%d",&n,&m,&S);
	rep(i,1,n) num[i]=read();
	int a,b;
	rep(i,1,n-1)
	{
		scanf("%d%d",&a,&b);
		adde(a,b);
		adde(b,a);
	}
	dfs(S,0);
	i8 v,w;
	while(m--)
	{
		scanf("%d%d",&a,&b);
		if(a==1)
		{
			v=read();
			w=2ll*v*num[b]+v*v;
			modify(inseg[b],w);
			modify(inseg[b]+siz[b],-w);
			num[b]+=v;
		}
		else
		{
			w=ask(inseg[b]);
			write((w+sum[b])%mod);
			putchar('\n');
		}
	}
	return 0;
}

I題:題目很短,求

\[\sum_{i=l}^rC_i^k \]

\[k\leqslant200,l,r\leqslant 10^9 \]

其實是個神仙題。。。把式子拆開看:

\[C_l^k+C_{l+1}^k+\cdots+C_r^k \]

這個式子看不出什麼來,對這個式子進行一次轉化,由

\[C_n^m=C_n^{n-m} \]

\[C_l^{l-k}+C_{l+1}^{l-k+1}+\cdots+C_r^{r-k} \]

\[=C_l^{l-k}+C_l^{l-k-1}+C_{l+1}^{l-k+1}+\cdots+C_r^{r-k}-C_l^{l-k-1} \]

前兩項可以由楊輝三角公式合併,此時變為

\[C_{l+1}^{l-k}+C_{l+1}^{l-k+1}+C_{l+2}^{l-k+2}+\cdots+C_r^{r-k}-C_l^{l-k-1} \]

此時新的兩項又可以同樣的方式合併,一直合併到最後,則原式等於

\[C_{r+1}^{r-k}-C_l^{l-k-1} \]

雖然已經化到最簡形式了,但是此時還是無法盧卡斯求。我們注意到k很小,並對組合數階乘展開,發現最後只需要計算小於200項的連乘和k階乘的逆元,於是連乘暴力求就完事了,階乘可以暴力也可以先預處理。

程式碼很簡單

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define i8 __int128
#define __int128 ll
#define rep(i,l,r) for(int i=l;i<=r;i++)
ll mod=1e9+7;
ll f[1005];
void init()
{
	f[0]=1;
	rep(i,1,988) f[i]=1ll*i*f[i-1],f[i]%=mod;
}
ll ksm(ll x,ll y)
{
	ll ret=1;
	while(y)
	{
		if(y&1) ret*=x,ret%=mod;
		x*=x,x%=mod;
		y>>=1;
	}
	return ret;
}
ll inv(ll x)
{
	return ksm(x,mod-2);
}
int main()
{
	int T;
	scanf("%d",&T);
	init();
	while(T--)
	{
		ll l,r,k;
		scanf("%lld%lld%lld",&l,&r,&k);
		ll ans1=1;
		rep(i,max(r-k+1,0ll),r+1) ans1*=1ll*i,ans1%=mod;
		ans1*=inv(f[k+1]),ans1%=mod;
		ll ans2=1;
		rep(i,max(0ll,l-k),l) ans2*=1ll*i,ans2%=mod;
		ans2*=inv(f[k+1]),ans2%=mod;
		ll ans=ans1-ans2;
		ans=(ans%mod+mod)%mod;
		printf("%lld\n",ans);
	}
	return 0;
}