noip模擬10[入陣曲·將軍令·星空](luogu)

fengwu2005發表於2021-06-29

對於這次考試來說,總體考得還是不錯的

就是有一個小問題,特判一定要判對,要不然和不判一樣,甚至錯了還會掛掉30分

還有一個就是時間分配問題,總是在前幾個題上浪費太多時間,導致最後一個題完全沒有時間思考

所以最後一個題我又成功的爆零了

下次衝第一,把分拿滿

當然最重要的還是

關於這個考場上是想正解還是暴力的問題

我現在有了一個大概的思路,就是

不要浪費太多時間去想,一般保持在一個小時以內是可以的

當然這是在前10分鐘已經把暴力思路弄出來的情況下(暴力程式可以先不著急打)






那下面就是正解時刻了

T1入陣曲

我個人認為這個題還是非常符合我的水平的

因為在考場上,我對這個題進行了百般的思考,雖然最終只有75pts,但是這個題確實讓我成長很多

起碼,我馬上就要學會如何在考場上順理成章的想出正解了

考場上想了很多,包括題目裡的矩陣乘(即使我在5min內把它否掉了)

我想到了餘數,也想到了餘數如何使用,所以我準備用一個二維樹狀陣列來實現 $ O(n^{2}logn) $的演算法

但是最後我遺憾的發現,這空間就是給我2G我也開不出來

然後我就直接放棄這麼做了,就打完暴力就溜走了,但是我的暴力比別人分高

我對全部都相同的情況進行了處理,然後拿到了15pts,嘿嘿

其實正解和我的思路是大同小異的,正解是$ O(n^3) $的,比我的樹狀陣列慢,但是能A題

就是先列舉這個矩形的上下邊界所在的行,就是$ x_1x_2\(的位置,此時複雜度是\) O(n^2) $的

然後下一層列舉列,此時列就可以當作一行算了,你已經求取了整個矩形的二位字首和(別告訴我你這都沒想到)

這時候我只需要記錄餘數相同的數有幾個,統計答案就好了

就做完了,記得不要用$ memset \(因為那樣的話複雜度就變成\) O(n^2k) $的了

由於$ k $非常的大是不能通過這個題的,所以我們記錄一下用過的餘數,一個一個清

\(code\)

#include<bits/stdc++.h>
using namespace std;
#define re register int
#define ll long long
const int N=405;
int n,m,k;
int a[N][N];
ll f[N][N],cnt[1000006],ans,tmp[N];
signed main(){
	scanf("%d%d%d",&n,&m,&k);
	for(re i=1;i<=n;i++)
		for(re j=1;j<=m;j++){
			scanf("%d",&a[i][j]);
			f[i][j]=f[i][j-1]+f[i-1][j]-f[i-1][j-1]+a[i][j];
		}
	for(re i=0;i<=n;i++)
		for(re j=i+1;j<=n;j++){
			for(re o=0;o<=m;o++){
				tmp[o]=(f[j][o]-f[i][o]+k)%k;
				ans+=cnt[tmp[o]];
				cnt[tmp[o]]++;
			}
			for(re o=0;o<=m;o++)cnt[tmp[o]]=0;
		}
	printf("%lld",ans);
}

答案很大,要用\(long long\)


\

T2將軍令

我也不知道這是我第幾次考場上想到正解但是沒拿全分了

害說實話我這主要是特判錯了,所以我以後在考場上對自己程式碼有信心的話,就不再特判了

我的方法說實話比較另類,正解其實很好理解,就是一個小貪心

\(O(2k)\)更新,然後總複雜度\(O(2kn)\)

其實我的方法比這種解法要少一個\(2k\),也就是\(O(n)\)的複雜度,因為k的範圍實在太小,我的程式碼也沒快到哪裡去

$ 我的解法也是貪心,當然可以dp,機房大佬已經搞出dp方程了,dp[x][i]表示在x節點,向下覆蓋i層的最小花費,$

$ 這樣的話,如果在每個點加上權值,這個題也就有解了。。。。$

我用一個maxn陣列記錄,此刻這個點到根的路徑上,或者是他的子樹內,深度最大的沒有被覆蓋的點

所以我們能想到,如果dep-maxn==k的時候,我們就應該在這個點放置小隊

然後我們就可以快快樂樂的進行dfs了

有四個轉移條件,因為當你在這個點放置小隊的時候,maxn=dep-k-1

而這個時候,我們就要對maxn和dep的關係進行討論,因為從兒子轉移過來,所以我們有四種情況、

設son[x]=y

	1、maxn[y]>=dep[x]&&maxn[x]>=dep[x]那我們就可以直接取最小的maxn了,因為你能覆蓋的我也能覆蓋,你不能覆蓋的我還能覆蓋
   
	2、maxn[y]>=dep[x]&&maxn[x]<dep[x]我們要看此時在其他兒子建立的小隊能不能覆蓋此時我這個兒子的最大沒有覆蓋的點
    
	3、maxn[y]<dep[x]&&maxn[x]>=de[x]我們就看這個兒子建立的小隊能不能覆蓋其他兒子的最大maxn

	4、maxn[y]<dep[x]&&maxn[x]<dep[x]那就直接去最大值

這樣的話我們就可以由兒子向父親\(O(1)\)轉移了,就完了,記得maxn初值為dep

\(code\)



#include<bits/stdc++.h>
using namespace std;
#define re register int 
const int N=1e5+5;
int n,k,t;
int to[N*2],nxt[N*2],head[N],rp;
void add_edg(int x,int y){
	to[++rp]=y;
	nxt[rp]=head[x];
	head[x]=rp;
}
int maxn[N],maxm[N],dep[N],maxx,fa[N],lr;
int ans;
void dfs3(int x,int f){
	maxn[x]=dep[x];
	for(re i=head[x];i;i=nxt[i]){
		int y=to[i];
		if(y==f)continue;
		fa[y]=x;dep[y]=dep[x]+1;
		dfs3(y,x);
		if(maxn[y]<dep[x]&&maxn[x]>dep[x]&&dep[x]-maxn[y]-1>=maxn[x]-dep[x])maxn[x]=maxn[y];
		if(maxn[y]<dep[x]&&maxn[x]<=dep[x])maxn[x]=min(maxn[x],maxn[y]);
		if(maxn[y]>dep[x]&&maxn[x]<dep[x]&&maxn[y]-dep[x]>dep[x]-maxn[x]-1)maxn[x]=maxn[y];
		if(maxn[y]>dep[x]&&maxn[x]>=dep[x])maxn[x]=max(maxn[x],maxn[y]);
	}
	if(maxn[x]-dep[x]==k){
		ans++;maxn[x]=dep[x]-k-1;
	}
}
signed main(){
	scanf("%d%d%d",&n,&k,&t);
	for(re i=1,x,y;i<n;i++){
		scanf("%d%d",&x,&y);
		add_edg(x,y);add_edg(y,x);
	}
	dep[1]=0;fa[1]=0;dfs3(1,0);
	if(maxn[1]>=dep[1])ans++;
	printf("%d",ans);
}



\

T3星空

啊啊啊這個題我改了好久啊

考完試半個小時我就把前兩個題搞定了,最後這個題確實思維量很大,我根本看不懂題解

不是我看不懂是題解根本說的就不對,氣死我了,害得我還看了半天,這ppt也不太行,說的沒譜

還是靠我自己幹出來

首先,我們看到這是個區間修改的操作,嘿嘿,第一眼線段樹,然後我發現,雖然快,但是dp不了啊

後來想到,狀壓,一看40000,壓不了果斷放棄,然後暴力分都沒拿到

但是正解確實是狀壓,,,為什麼呢??

首先,區間換單點,對於序列來說,區間轉單點的最好辦法不過是差分和字首和

而這個題明顯是要用差分的,而取反操作,就是異或操作,我們令\(cf[i]=a[i] ^\wedge a[i-1]\)

我們操作完之後,所有\(cf[i]\)\(1\)的地方,都是此時燈與前一個狀態不同的地方,所以我們的目的就是把這些1都幹掉

那麼我們就發現,這個k之有8啊就算每一個燈都造成2個1的貢獻,那麼最大也只有16,這時候就是狀壓dp了

我們操作時只能將兩個相距為特定值的點做取反操作,所以我們為了不做無謂的動作,每一次操作的兩個數至少會有一個1

然後我們就可以想到,由其中一個1到達另外一個1的最小步數是多少,這時候我們就搬出好久不用的bfs

畢竟是最小步數,一層一層的搜找到的必然是最優解

然後我們就對這2k個1每個點分別進行bfs,複雜度是\(O(kmn)\)

連邊就連這個點對於每一種操作的位置,包括前面和後面,都連上

然後我們就可以愉快的狀壓了

我們設dp[s]表示狀態為s的最小步數,0表示沒有消掉這一位上的1,1表示已經消掉

然後就轉移就行了,其實這個dp我也想了好久,畢竟狀壓我也好久沒打了

\(code\)



#include<bits/stdc++.h>
using namespace std;
#define re register int
#define ll long long
const int N=40005;
const int K=16;
int n,k,m;
int a[N],b[N],cf[N];
int off[N],cnt,pos[N];
int to[N*65*2],nxt[N*65*2],head[N],rp;
void add_edg(int x,int y){
	to[++rp]=y;
	nxt[rp]=head[x];
	head[x]=rp;
}
bool vis[N];
ll dis[K+5][K+5],dp[1<<K];
struct node{
	int x;ll len;
};
queue<node> q;
void bfs(int w,int hea){
	memset(vis,false,sizeof(vis));
	vis[w]=1;q.push((node){w,0});
	while(!q.empty()){
		node now=q.front();q.pop();
		for(re i=head[now.x];i;i=nxt[i]){
			int y=to[i];
			if(vis[y])continue;
			vis[y]=true;
			if(cf[y])dis[hea][pos[y]]=now.len+1;
			q.push((node){y,now.len+1});
		}
	}
}
signed main(){
	scanf("%d%d%d",&n,&k,&m);
	for(re i=1,x;i<=k;i++)scanf("%d",&x),a[x]=1;
	for(re i=1;i<=m;i++)scanf("%d",&b[i]);
	for(re i=1;i<=n+1;i++){
		cf[i]=a[i]^a[i-1];
		if(cf[i])off[++cnt]=i,pos[i]=cnt;
	}
	for(re i=1;i<=n+1;i++){
		for(re j=1;j<=m;j++){
			if(i+b[j]<=n+1)add_edg(i,i+b[j]);
			if(i-b[j]>0)add_edg(i,i-b[j]);
		}
	}
	memset(dis,0x3f,sizeof(dis));
	for(re i=1;i<=cnt;i++)bfs(off[i],i);
	memset(dp,0x3f,sizeof(dp));dp[0]=0;
	for(re i=0;i<(1<<cnt);i++){
		int p=0;
		while(i&(1<<p))p++;
		for(re j=p+1;j<=cnt;j++){
			if(!(i&(1<<j)))
				dp[i|(1<<j)|(1<<p)]=min(dp[i|(1<<j)|(1<<p)],dp[i]+dis[p+1][j+1]);
		}
	}
	printf("%lld",dp[(1<<cnt)-1]);
}

然後就完了。。。。。。。