【FCS NOI2018】福建省冬摸魚筆記 day5

第五天,也是講課的最後一天。數組

數據結構專題,講師:楊志燦數據結構

他的blog我彷佛找不到了……之前確定是在百度博客裏面。可是如今百度博客消失了。優化

PPT作的頗有感受,說了不少實用的技巧。ui

我以爲實際上是收穫最大的一天,由於聽懂了XDspa


中午划水code


下午的題也很是良心,然而@ghostfly233@Melacau說他們作過原題???blog

問題就是很是卡常!n=10^5的nlogn題目,400組詢問,4秒?6億複雜度啊!排序

然而就是跑過了,我也沒辦法,樹狀數組複雜度不滿……遊戲

矩陣快速冪也卡……很是難受。get

【T1】

題意:有\(1\leq a\leq b\leq c\leq n\),問多少個\((a,b,c)\)三元組知足\(a+b^2\equiv c^3(mod\;k)\)。

題解:枚舉b,發現a+b^2的範圍是b^2+1到b^2+b是連續的一段,考慮計算這段中的c大於等於b的c^3的個數。

顯然用樹狀數組維護,複雜度有點問題,但是就是AC了XD。代碼很是的優美。

#include<cstdio>
#include<cstring>
int n,k,bit[100001];
long long ans;
inline void I(int i){for(++i;i<=k;i+=i&-i)++bit[i];}
inline int Q(int i){int s=0;for(++i;i;i-=i&-i)s+=bit[i];return s;}
int main(){
	freopen("exclaim.in","r",stdin);
	freopen("exclaim.out","w",stdout);
	int T; scanf("%d",&T);
	for(int t=1;t<=T;++t){
		scanf("%d%d",&n,&k); ans=0;
		memset(bit,0,sizeof bit);
		int sum=0;
		for(int i=n;i;--i){
			I(1ll*i*i*i%k); ++sum;
			long long l=1ll*i*i+1, r=1ll*i*i+i;
			ans+=1ll*(r/k-l/k)*sum+Q(r%k)-Q(l%k-1);
		}
		printf("Case %d: %lld\n",t,ans);
	}
	return 0;
}

【T2】

題意:你玩一個多結局RPGgalgame(???),遊戲流程相似樹形結構,你總共通關了k次,每一個場景你都會得到該場景的價值(可能還有好感度和浪費手紙?),可是屢次經歷該場景就只有一次價值了。

價值都是正整數。

問他怎樣攻略能得到最大價值?

題解:有一個貪心思路:每次都走最大價值的路線,這樣必定最終價值也最大。

如何證實?

咱們先證實最大的必定要選吧,這樣後面的概括一下就完了。

先分類討論:

假設咱們沒有選擇最大的,那麼咱們從最大的這條路線往回回溯,遇到第一個有選取的場景,那麼走這個場景的這條(任意一條)路線,均可以換成走最大的路線。

爲何?假設走到這個場景的路線只有一條,那麼顯然更優,由於前面的價值都同樣,換成這條會更優。

假設有多條,那麼其中任意一條換掉以後,多了最優的這一條的價值收益,而且本身這條的收益還不用徹底減掉,由於還有其餘的路線走過,因此比上一種狀況還來得優。

那麼咱們就證實了,最大的路線必定要選,接下來把最大的路線的收益刪去,變成0,那麼這時再選取最大的仍是同樣的。因而證實完畢。

那麼如何處理呢?包括查詢最大收益和修改。

其實這就是一個線段樹可以維護的:

把節點按照DFS序排序,一個點的全部孩子就是一段區間。

把全部的葉子節點(結局)按照DFS序扔進線段樹,初始值就是路線收益,查詢時直接查最大值,修改時,咱們知道一個點的孩子對應一段區間,因此區間減法便可。

查詢k次,每次O(1),修改最多n次,每次O(log 葉子節點數)。

那麼這題就算作完了。

#include<cstdio>
#include<algorithm>
using namespace std;
int n,k,a[200001],fa[200001],t[200001],cnt;
int h[200001],to[200001],nxt[200001],tot;
inline void ins(int x,int y){nxt[++tot]=h[x];to[tot]=y;h[x]=tot;}
int L[200001],R[200001],vis[200001];
long long dat[524289],lazy[524289],ans;
int pos[524289];
void dfs(int u){
	if(!h[u]) {L[u]=R[u]=++cnt, t[cnt]=u; return;}
	L[u]=n; for(int i=h[u];i;i=nxt[i]) dfs(to[i]), L[u]=min(L[u],L[to[i]]), R[u]=max(R[u],R[to[i]]);
}
inline void pushdown(int i){
	lazy[i<<1]+=lazy[i];
	lazy[i<<1|1]+=lazy[i];
	dat[i<<1]+=lazy[i];
	dat[i<<1|1]+=lazy[i];
	lazy[i]=0;
}
void build(int i,int l,int r){
	pos[i]=l;
	if(l==r) return;
	int mid=l+r>>1;
	build(i<<1,l,mid), build(i<<1|1,mid+1,r);
}
void Ins(int i,int l,int r,int a,int b,int v){
	if(a<=l&&r<=b) {dat[i]+=v; lazy[i]+=v; return;}
	if(r<a||b<l) return;
	pushdown(i); int mid=l+r>>1;
	Ins(i<<1,l,mid,a,b,v); Ins(i<<1|1,mid+1,r,a,b,v);
	dat[i]=max(dat[i<<1],dat[i<<1|1]);
	pos[i]=dat[i<<1]>=dat[i<<1|1]?pos[i<<1]:pos[i<<1|1];
}
int main(){
	freopen("game.in","r",stdin);
	freopen("game.out","w",stdout);
	scanf("%d%d",&n,&k); k=min(k,n);
	for(int i=1;i<=n;++i) scanf("%d",a+i);
	for(int i=1,x,y;i<n;++i) scanf("%d%d",&x,&y), ins(x,y), fa[y]=x;
	dfs(1);
	build(1,1,cnt);
	for(int i=1;i<=n;++i) Ins(1,1,cnt,L[i],R[i],a[i]);
	for(int i=1;i<=k;++i){
		int p=t[pos[1]];
		ans+=dat[1];
		int sum=0;
		while(p&&!vis[p]) Ins(1,1,cnt,L[p],R[p],-a[p]), vis[p]=1, sum+=a[p], p=fa[p];
	}
	printf("%lld",ans);
	return 0;
}

【T3】

題意:求有向圖中長度小於k的環的個數,沒有自環。

題解:簡單矩陣,有著名定理支持:鄰接矩陣的k次方就是從一個節點出發,通過k步到達另外一個節點的路徑數。

即求\(A^1+A^2+\cdots+A^{k-1}\)。

簡單矩陣快速冪便可,加一個稀疏矩陣優化,跑的飛快。根本不須要分治。

#include<cstdio>
#include<cstring>
int n,n2,k,p,ans;
struct Mat{int fk[200][200];}M1,M2;
inline Mat operator*(Mat p1,Mat p2){
	Mat p3;memset(p3.fk,0,sizeof p3.fk);
	for(register int i=0;i<n2;++i)
	for(register int k=0;k<n2;++k) if(p1.fk[i][k])
	for(register int j=0;j<n2;++j)
		p3.fk[i][j]=(p3.fk[i][j]+1ll*p1.fk[i][k]*p2.fk[k][j])%p;
	return p3;
}
int main(){
	freopen("tour.in","r",stdin);
	freopen("tour.out","w",stdout);
	scanf("%d",&n); n2=n<<1;
	char ch;
	for(register int i=0;i<n;++i) M1.fk[i][i]=M2.fk[i][i]=M2.fk[i+n][i+n]=1;
	for(register int i=n;i<n2;++i) for(register int j=0;j<n;++j) (ch=getchar())=='Y'?M1.fk[i][j]=M1.fk[i][j+n]=1:(ch!='N'?--j:0);
	scanf("%d%d",&k,&p); --k;
	if(p==1){puts("0");return 0;}
	while(k){
		if(k&1) M2=M2*M1;
		k>>=1; M1=M1*M1;
	}
	for(register int i=0;i<n;++i) ans=(ans+M2.fk[i+n][i])%p;
	printf("%d",ans);
	return 0;
}
相關文章
相關標籤/搜索