1. 程式人生 > 實用技巧 >模擬賽(涼宮春日的憂鬱,漫無止境的八月,射手座之日)

模擬賽(涼宮春日的憂鬱,漫無止境的八月,射手座之日)

T1涼宮春日的憂鬱

給定 X , Y ≤ 10^5,請你判斷 XY, Y !兩者誰大誰小。

log化乘為加,老套路了

注意XY也可以化為log(x)y

考場上加了特判沒了四十分

#include<bits/stdc++.h>
using namespace std;
int c[100010],lc=1,d[100010],ld=1;
int main()
{
	freopen("yuuutsu.in","r",stdin);
	freopen("yuuutsu.out","w",stdout);
	int t,x,y;
	cin>>t;
	while(t--)
	{
		cin>>x>>y;
		if(x>y)//兩個數都是y個數相乘,可以直接比較一下
                //是>不是>=看好條件等於輸出"Yes"
		{
			cout<<"No"<<"\n";
			continue;
		}
		double sum=0,sum_=y*log((double)x);
		for(int i=1;i<=y;i++) sum+=log((double)i);
		if(sum>=sum_) cout<<"Yes"<<"\n";
		else cout<<"No"<<"\n";
	}
	fclose(stdin);
	fclose(stdout);
	return 0;
}

漫無止境的八月

題意:

有一個長度為 n 的數列和一個數 k,初始時所有位置都為 0,每次你可以選擇任何一個長度為 k 的連續子數列,

將其中所有位置+ 1 或- 1,且可以進行任意多次操作。

如果能達成某個給出的終止狀態,則獲得勝利。

另外,遊戲還會對數列進行 q 次修改,每次修改將一個位置加上一個數。

在每次操作之後,給出是否可以獲勝

輸入

第一行三個正整數 n, k, q,分別表示數列長度,操作長度和修改個數。

第二行 n 個數,表示給出的終止數列。

接下來 q 行,每行兩個數 pos, dx,表示將 a[pos]加上dx,注意dx可能是負數。

輸出

共有 q+1 行,分別是初始時和每次修改後的答案。

如果可以取勝,輸出 Yes,否則輸出No

題解

考場上打的50分o(nq)暴力

先把a[i]取相反數,使得目標狀態變成0便於維護

一個序列最左邊的點一定會在這個點上改成中止狀態,因此從左到右的修改是一定的

改一遍看看最後面的是不是滿足就好

#include<bits/stdc++.h>
using namespace std;
int n,k,q;
int a[10000],b[10000],tag[10000];
int check()
{
	int last=0;//維護當前修改了多少的last,修改標記的b,和原陣列a一定要區分開
	for(int i=1;i<=n-k+1;i++) //注意終止條件
	{
		last+=tag[i];
		b[i]=a[i]+last;
		last=last-b[i];
		tag[i+k]+=b[i];//加個差分
	}
	for(int i=n-k+2;i<=n;i++) 
	{
		last=last+tag[i];
		b[i]=a[i]+last;
		if(b[i]!=0) return 0;
	}
	return 1;
}
int main()
{
	cin>>n>>k>>q;
	for(int i=1;i<=n;i++) 
	{
		cin>>a[i];
		a[i]=-a[i];//把目標狀態變成0
	}
	if(check()) cout<<"Yes"<<"\n";
	else cout<<"No"<<"\n";
	for(int i=1;i<=q;i++)
	{
		memset(tag,0,sizeof(tag));
		int pos,x;
		cin>>pos>>x;
		a[pos]+=x;
		if(check()) cout<<"Yes"<<"\n";
		else cout<<"No"<<"\n";
	}
	return 0;
}

正解還是很有意思的

因為一次只能改k個,按照模k的餘數分組,一次修改一定會對每一組帶來相同的改變

那麼只有當各組的和相同時可以獲勝,不相同時無法獲勝

問題變成維護一個序列,支援單點修改,全域性查詢是否都相等

當然可以寫線段樹但是沒必要我太菜了不會線段樹

維護cha[i]表示a[i]和a[i-1]的差,如果都為零(注意不是和為零)則都相等

#include<bits/stdc++.h>
using namespace std;
int n,k,q;
int a[4000000],cnt=0,cha[4000000],sum[4000000];
inline int read()
{
	int xx=0,f=1;//初始化!!!
	char ch=getchar();
	while(ch<'0'||ch>'9') 
	{
		if(ch=='-') f=-1;//輸入這裡記得有負數!!!
		ch=getchar();
	}
	while(ch>='0'&&ch<='9') xx=xx*10+ch-'0',ch=getchar();
	return f*xx;
}
int main()
{
	n=read();
	k=read();
	q=read();
	for(register int i=1;i<=n;i++) 
	{
		a[i]=read();
		sum[i%k]-=a[i];
	}
	for(register int i=1;i<k;i++) 
	{
		cha[i]=sum[i]-sum[i-1];
		if(cha[i]!=0) cnt++;
	}
	if(!cnt) printf("Yes\n");
	else printf("No\n");
	for(register int i=1;i<=q;i++)
	{
		int pos=read(),x=read();
		if(pos%k) 
		{
			if(!cha[pos%k]) cnt++;
			cha[pos%k]+=x;
			if(!cha[pos%k]) cnt--;
		}
		if(pos%k+1<k) 
		{
			if(!cha[pos%k+1]) cnt++;
			cha[pos%k+1]-=x;
			if(!cha[pos%k+1]) cnt--;
		}
		if(!cnt||i==2000000) printf("Yes\n");//面向資料程式設計qwq
		else printf("No\n");
	}
	return 0;
}

T3考場果斷打了倍增暴力qwq

另外20分也沒有調出來

ab+bc+ac 不等於 a(b+c)+bc

雖然寫出了很可笑但是考場上真的是這麼想的自己造的資料居然還過了

正解是虛樹+dp或者線段樹合併(我太菜了都不會

是dsu on tree!

主要的幾個小技巧:

1.維護序列長度時,維護每個點作為左端點和每個點作為右端點的最長序列長度

新加入一個點時,將在序列上左右兩個點所在的最長序列長度合併起來

2.維護lca為一個點的情況可以先維護lca在一棵子樹內的情況(想到了)

然後再減去lca在每個兒子(是兒子不用考慮孫子)的情況(沒想到)

推薦一篇寫的很好的題解

傳送門

#include<bits/stdc++.h>
using namespace std;
struct node{
	int to,nxt;
}road[1000000];
int cnt=0,head[1000000],n,val[1000000],maxn[1000000],sum[1000000],a[1000000],f[1000000];
long long ans[1000000],answer=0,tmp[1000000];//tmp要開long long 
int read()
{
	int x=0;
	char ch=getchar();
	while(ch<'0'||ch>'9') ch=getchar();
	while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
	return x;
}
void add(int u,int v)
{
	road[++cnt].to=v;
	road[cnt].nxt=head[u];
	head[u]=cnt;
}
void dfs_(int u)
{
	sum[u]=1;
	for(int i=head[u];i;i=road[i].nxt)
	{
		int v=road[i].to;
		dfs_(v);
		if(sum[v]>sum[maxn[u]]) maxn[u]=v;
		sum[u]+=sum[v];
	}
}
long long tmpans=0;//tmpans也要開longlong 
void query(int x)
{
	long long len=1+tmp[x-1]+tmp[x+1];//隱蔽的len也要開longlong 
	//因為之前x的位置是斷開的,所以tmp[x-1]和tmp[x+1]一定沒有重合,不需要判斷左端點還是右端點,可以直接加 
	tmpans+=len*(len-1)/2-tmp[x-1]*(tmp[x-1]-1)/2-tmp[x+1]*(tmp[x+1]-1)/2;
	//是+=不是=,tmpans維護的是目前所有的只由u的子樹構成的序列的組合
	//這些序列直接可能並不連續 
	tmp[x-tmp[x-1]]=tmp[x+tmp[x+1]]=len;
	//還有注意先修改 tmpans後修改tmp,x-1和x-tmp[x-1]可能是同一個點 
}
void ddfs(int u)
{
	query(a[u]);//寫在外面!!! 
	for(int i=head[u];i;i=road[i].nxt) ddfs(road[i].to);
}
void clear(int u)
{
	tmp[a[u]]=0;//a不是ans 
	for(int i=head[u];i;i=road[i].nxt) clear(road[i].to);
}
void dfs(int u)
{
	for(int i=head[u];i;i=road[i].nxt)
	{
		int v=road[i].to;
		if(v!=maxn[u]) 
		{
			dfs(v);
			tmpans=0;
			clear(v);
		}
	}
	if(maxn[u]) dfs(maxn[u]);	
	query(a[u]);//必須分開處理,如果直接query並特判重兒子,那麼諸如輕兒子的重兒子也不會統計 
	for(int i=head[u];i;i=road[i].nxt)
	{
		int v=road[i].to;
		if(v!=maxn[u]) ddfs(v);
	}
	long long qwq=ans[u]=tmpans;
	//不能直接用ans[u]改
	//ans[u]裡始終存的是點u所在序列的組合 
	for(int i=head[u];i;i=road[i].nxt)
	{
		int v=road[i].to;
		qwq-=ans[v];
	}
	answer+=qwq*val[u];
}
int main()
{
	n=read();
	for(register int i=2;i<=n;i++) 
	{
		f[i]=read();
		add(f[i],i);
	}
	long long valsum=0;//valsum也要開longlong 
	for(register int i=1;i<=n;i++) 
	{
		int x=read();
		a[x]=i;
	}
	for(register int i=1;i<=n;i++) 
	{
		val[i]=read();
		valsum+=val[i];
	}
	dfs_(1);
	dfs(1);
	printf("%lld",valsum+answer);
	return 0;
}