NOIP模擬題匯總(加厚版)


\(NOIP\)模擬題匯總(加厚版)

T1 string

描述

有一個僅由 '0' 和 '1' 組成的字符串 \(A\),可以對其執行下列兩個操作:

  • 刪除 \(A\)中的第一個字符;
  • \(A\)中 '1' 出現的次數為奇數,則在 \(A\) 的末尾插入 '1',否則在 \(A\) 的末尾插入 '0'.

現在給出一個同樣僅由 '0' 和 '1' 組成的字符串 \(B\),請你判斷 \(A\) 能否在任意多次操作后轉化為 \(B\)

輸入

輸入文件第一行一個正整數 \(T\),表示有 \(T\) 組數據需要進行判斷。

接下來依次輸入 \(T\) 組數據,每組數據有兩行,其中第一行包含一個字符串 \(A\),第二行包含一個字符串 \(B\)

輸出

輸出共 \(T\) 行,對應 \(T\) 組詢問。若第 \(i\) 組數據中 \(A\)串可以轉化為 \(B\) 串則輸出 "YES";否則輸出 "NO"。

輸入樣例 1

2
01011
0110
0011
1110

輸出樣例 1

YES
NO

提示

對於 \(20%\) 的數據,\(n\)\(10\), \(T\)=\(3\)

對於 \(50%\) 的數據,\(n\)\(1000\), \(T\)\(20\)

對於 \(100%\) 的數據,\(n\)\(10^6\), \(T\)\(50\)

題解

一道水題,但我考試的時候沒想出來

自己手推幾組樣例,就會發現一個規律:ans只與01串中\(1\)的個數有關

因為你可以講前面的數刪掉加到后面形成組合;

所以如果\(A\)\(1\)的個數大於等於\(B\)\(1\)的個數則輸出\(YES\),否則輸出\(NO\)

但是要注意若\(A\)\(1\)的個數為奇數要拿\((\)\(A\)\(1\)的個數+1\()\)\(B\)\(1\)的個數比較即可;

code:
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<cctype>
#define ll long long
#define R register
using namespace std;
template<typename T>inline void read(T &a){
    char c=getchar();T x=0,f=1;
    while(!isdigit(c)){if(c=='-')f=-1;c=getchar();}
    while(isdigit(c)){x=(x<<1)+(x<<3)+c-'0';c=getchar();}
    a=f*x;
}
char a[N],b[N];
int lena,lenb,A,B,t;
int main(){
	freopen("string.in","r",stdin);
	freopen("string.out","w",stdout);
	read(t);
	while(t--){
		A=0;B=0;
		scanf("%s%s",(a+1),(b+1));
		lena=strlen(a+1);
		lenb=strlen(b+1);
		for(R int i=1;i<=lena;i++)
			if(a[i]=='1')A++;
		for(R int i=1;i<=lenb;i++)
			if(b[i]=='1')B++;
		if(A&1)A++;
		if(A>=B)printf("YES\n");
		else printf("NO\n");
	}
    return 0;
}

T2 glide

描述

大森林中茂盛地生長着 \(N\) 棵樹,從 \(1\)\(N\) 編號,第 \(i\) 棵樹的高度是 \(H_i\),住在這片森林里的飛鼠 Makik 可以在樹與樹之間滑翔。

\(M\) 組樹能供 \(Makik\)在其間滑翔,在每組樹間滑翔的時間是固定的。滑翔時,\(Makik\) 每秒會下落 \(1\) 米,即當 \(Makik\) 當前位置距離地面 \(h\) 米時,它經過 \(t\) 秒的滑翔后會落到距離地面 \(h-t\)米的高度上。但若\(h-t\) 小於 \(0\) 或比要到達的樹高還要大,則 \(Makik\) 不能進行這次滑行。

除滑翔外,\(Makik\) 還可以沿着一棵樹爬上爬下,每向上或向下移動 \(1\) 米均需花費 \(1\) 秒的時間。現在,\(Makik\) 想要從第 \(1\) 棵樹上高度為 \(X\) 米的位置移動到第 \(N\) 棵樹的頂端(高度為 \(H_N\) 的位置),請你幫它算一算最少要花費多少秒的時間。

輸入

輸入文件第一行包含三個整數 \(N\),\(M\),\(X\),分別表示樹的數量、能供 \(Makik\) 在其間滑翔的樹的組數和 \(Makik\) 的初始高度。

下面 \(N\) 行,其中第 \(i\) 行包含一個整數,表示第 \(i\) 棵樹的高度 \(H_i\)

接下來 \(M\) 行,其中 \(j\) 行包含三個整數 \(A_j\),\(B_j\),\(T_j\)(\(A_j\)\(\neq\) \(B_j\)),表示 \(Makik\) 可以在第 \(A_j\) 棵樹和第 \(B_j\) 棵樹間相互滑行,每次滑行的時間是 \(T_j\) 秒。數據保證每一組樹都不會被多次給出。

輸出

輸出一行一個整數,表示 \(Makik\) 到達第 \(N\) 棵樹所至少要花費的秒數。如果不能到達,輸出 \(-1\)

題解

我這道題在最短路上搞了一些操作,感覺想貪心,感覺不對,但是AC了;

輸入樣例 1

>5 5 0
>50
>100
>25
>30
>10
>1 2 10
>2 5 50
>2 4 20
>4 3 1
>5 4 20
>```

輸出樣例 1

110

提示

樣例說明:

先沿第 \(1\) 棵樹向上爬 \(50\) 米,再依次滑翔到第 \(2\) 棵、第 \(4\) 棵、第 \(5\) 棵樹上,最后沿第 \(5\) 棵樹向上爬 \(10\) 米。

數據規模與約定

對於 \(30\%\) 的數據,\(N\leq 1000\), \(M\leq 3000\), \(H_i\leq 100\), \(T_j\leq 100\)

對於另外 \(30\%\) 的數據,\(X=0\)

對於 \(100 \%\) 的數據,\(2\leq N\leq 100000\), \(1\leq M\leq 300000\), \(1\leq H_i\leq 10^9\), \(1\leq T_j\leq 10^9\), \(0\leq X\leq H_1\)

題解

std講的思路:

一直跑\(SPFA\)(或\(Dij\)堆優化),最后\(ans\)=(地底下的距離乘以2+地上的距離);

std’s code:
#include <bits/stdc++.h>
using namespace std;

#define N 100005
#define M 600005
#define _inf -4557430888798830400ll

int n, m, h[N];
long long X, d[N];

priority_queue<pair<long long, int> > q;

int head[N];

struct graph
{
	int next, to, val;
	graph() {}
	graph(int _next, int _to, int _val)
	: next(_next), to(_to), val(_val) {}
} edge[M];

inline void add(int x, int y, int z)
{
	static int cnt = 0;
	edge[++cnt] = graph(head[x], y, z);
	head[x] = cnt;
}

void spfa(int s)
{
	memset(d, 0xc0, sizeof d);
	d[s] = X;
	q.push(make_pair(X, s));
	while (!q.empty())
	{
		int x = q.top().second; long long y = q.top().first; q.pop();
		if (y != d[x]) continue;
		for (int i = head[x]; i; i = edge[i].next)
			if (edge[i].val <= h[x])
			if (d[edge[i].to] < min(y - edge[i].val, 1ll * h[edge[i].to]))
				d[edge[i].to] = min(y - edge[i].val, 1ll * h[edge[i].to]),
				q.push(make_pair(d[edge[i].to], edge[i].to));
	}
}

int main()
{
	cin >> n >> m >> X;
	for (int i = 1; i <= n; ++i)
		scanf("%d", &h[i]);
	for (int x, y, z, i = 1; i <= m; ++i)
		scanf("%d%d%d", &x, &y, &z),
		add(x, y, z), add(y, x, z);
	spfa(1);
	if (d[n] == _inf) cout << -1 << endl;
	else cout << (X - d[n]) * 2 - X + h[n] << endl;
	return 0;
}

我的思路:

老師的思路中細節比較少但可能不太好想,但我的寫法比較好理解 \((*/ω\*)\)

我的思路就是在\(SPFA\)中模擬一個\(H\)數組,\(H_i\)表示跑最短路時,在\(i\)點時的高度(就是你以最短路去走到\(i\)點時的高度)。

我認為就是在最短路時模擬......(細節比較多)

my code:
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<cctype>
#include<queue>
#define ll long long
#define R register
#define N 300005 
#define INF 0x7fffffffffLL
using namespace std;
template<typename T>inline void read(T &a){
    char c=getchar();T x=0,f=1;
    while(!isdigit(c)){if(c=='-')f=-1;c=getchar();}
    while(isdigit(c)){x=(x<<1)+(x<<3)+c-'0';c=getchar();}
    a=f*x;
}
int n,m,x,head[N],tot,flag,h[N],H[N],vis[N],u,v,w;
ll dist[N];
inline int abss(R int x){return x<0?-x:x;}
struct node{
	int nex,to,dis;
}edge[N<<1];
inline void add(R int u,R int v,R int w){
	edge[++tot].nex=head[u];
	edge[tot].to=v;
	edge[tot].dis=w;
	head[u]=tot;
}
inline void spfa(R int s){
	queue<int>q;
	for(R int i=1;i<=n;i++)dist[i]=INF;
	q.push(s);vis[s]=1;dist[s]=0;H[s]=x;
	while(!q.empty()){
		R int x=q.front();q.pop();vis[x]=0;
		for(R int i=head[x];i;i=edge[i].nex){
			R int xx=edge[i].to;
//-------------下面是模擬判斷 細節比較多-------------------------------------------------------
             R int now=0;//now是模擬你需要爬多少步  正數為向上爬  負數為想下爬
			if(H[x]-edge[i].dis<0)now=(edge[i].dis-H[x]);//以當前高度跳到要更新的點會跳到地下
			else if(H[x]-edge[i].dis>h[xx])now=(-1)*(H[x]-edge[i].dis-h[xx]);//跳到天上
			if(H[x]+now>h[x]||H[x]+now<0)continue;//判斷當前能不能爬(不能爬到地下或天上)
			if(dist[xx]>dist[x]+edge[i].dis+abss(now)){
				dist[xx]=dist[x]+edge[i].dis+abss(now);
				H[xx]=H[x]+now-edge[i].dis;// 實時更新H數組
				if(!vis[xx]){
					vis[xx]=1;
					q.push(xx);
				}
			}
		}
	}
}
int main(){
	freopen("glide.in","r",stdin);
	freopen("glide.out","w",stdout);
	read(n);read(m);read(x);
	for(R int i=1;i<=n;i++)read(h[i]);
	for(R int i=1;i<=m;i++){
		read(u);read(v);read(w);
		add(u,v,w);add(v,u,w);
	}
	spfa(1);
	if(dist[n]==INF)printf("-1\n"); 
	else printf("%lld\n",dist[n]+h[n]-H[n]);//因為題中說要爬到第n棵樹的樹頂
    return 0;
}

T3 vestige

描述

作為魔導士的你進入了一片遺跡。遺跡內共有 \(n\) 個密室,編號從 \(1\)\(n\)。密室間由 \(m\) 條雙向通道相連通,通道的長度不盡相同。由於遺跡已塵封多年,通道里都充滿着污穢。你需要先凈化掉所有通道里的污穢,才能邁出探索的腳步。

首先,你可以使用神聖驅魔術,凈化以 \(1\) 號密室為中心一定區域內所有的道路。當你選定一個非負整數距離 \(X\) 后,消耗 \(C\times X\) 個魔力點(\(C\) 為常數),此時若一條通道兩端的密室與 \(1\) 號密室的距離均不大於 \(X\),這條通道就會被凈化。之后,你需要分別凈化其余通道,凈化一條通道所需魔力點數即為這條通道的長度。

請確定凈化的方式,使消耗的魔力點數盡可能少。

輸入

輸入文件第一行包含三個正整數 \(n\), \(m\), \(C\),分別表示密室數量、通道數量和與使用神聖凈化術相關的常數。

接下來 \(m\) 行,其中第 \(i\) 行包含 \(3\) 個整數 \(u_i\), \(v_i\), \(d_i\),依次表示第 \(i\) 條通道兩端密室的編號和這條通道的長度。

輸出

輸出一行一個整數,表示消耗魔力點數的最小值。

輸入樣例 1 :

5 5 2
2 3 1
3 1 2
2 4 3
1 2 4
2 5 5

輸出樣例 1 :

14

提示

對於 \(32\%\) 的測試數據,\(n\)\(100\), \(m\)\(200\), \(C\)\(100\), \(di\)\(10\)

對於 \(64\%\) 的測試數據,\(n\)\(100\), \(m\)\(4000\)

對於 \(100\%\) 的測試數據,滿足以下條件:\(2≤n≤100000, 1≤m≤200000, 1≤C≤100000,u_i≠v_i,1≤d_i≤100000\),在兩個密室間直接連接的通道不超過一條。

題解

我的歪解

我首先想的是分治,我想二分肯定不行,因為它是沒有單調性的。

我想了一下感覺它的大部分數據應該是有凸性的(例如\(y=x^2\)的函數圖像),所以可以三分。

下面是我的三分代碼(得了84分,可以說騙了不少,當時手賤\(SPFA\)寫錯了,竟有68分

三分模板沒過的我居然瞎歪歪了一個三分

歪解code:
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<cctype>
#include<queue>
#define ll long long
#define R register
#define N 400005
#define INF 0x7fffffffffffLL
using namespace std;
template<typename T>inline void read(T &a){
    char c=getchar();T x=0,f=1;
    while(!isdigit(c)){if(c=='-')f=-1;c=getchar();}
    while(isdigit(c)){x=(x<<1)+(x<<3)+c-'0';c=getchar();}
    a=f*x;
}
ll n,m,c,tot,h[N],vis[N],pd[N];
ll dist[N],sum,now_ans,now;
struct bian{
	int u,v;
	ll w;
}b[N];
struct node{
	int nex,to;
	ll dis;
}edge[N<<1];
inline void add(R int u,R int v,R ll w){
	edge[++tot].nex=h[u];
	edge[tot].to=v;
	edge[tot].dis=w;
	h[u]=tot;
}
inline void spfa(R int s){
	for(R int i=1;i<=n;i++)dist[i]=INF;
	queue<int> q;q.push(s);dist[s]=0;vis[s]=1;
	while(!q.empty()){
		R int x=q.front();q.pop();vis[x]=0;
		for(R int i=h[x];i;i=edge[i].nex){
			R int xx=edge[i].to;
			if(dist[xx]>dist[x]+edge[i].dis){
				dist[xx]=dist[x]+edge[i].dis;
				if(!vis[xx]){
					vis[xx]=1;
					q.push(xx);
				}
			}
		}	
	}
}
inline ll check(R ll mid){
	ll tot=0;
	for(R int i=1;i<=n;i++)pd[i]=0;
	for(R int i=1;i<=n;i++)
		if(dist[i]<=mid)pd[i]=1;
	for(R int i=1;i<=m;i++)
		if(pd[b[i].u]&&pd[b[i].v])
			tot+=b[i].w;
	return tot-mid*c;//這是你能節省的
}
int main(){ 
	freopen("vestige.in","r",stdin);
	freopen("vestige.out","w",stdout);
	read(n);read(m);read(c);
	for(R int i=1;i<=m;i++){
		read(b[i].u);read(b[i].v);read(b[i].w);
		add(b[i].u,b[i].v,b[i].w);add(b[i].v,b[i].u,b[i].w);sum+=b[i].w;
	}
	spfa(1);
	R ll l=0,r=sum;
	while(l<=r){
		R ll tmp=(r-l)/3;
		R ll mid1=l+tmp;
		R ll mid2=r-tmp;
		if(check(mid1)>check(mid2)) r=mid2-1;
		else l=mid1+1;
	}
	ll tmp=check(l),temp=check(r);
	if(tmp>temp)now=l,now_ans=tmp;
	else now=r,now_ans=temp;
	printf("%lld\n",sum-now_ans);
    return 0;
}

當然了,三分本來就是一個非常好的騙分算法(也會是正解),有些題在加一些暴力,一定會有神奇的效果;

講課老師說加上暴力這道題應該可以\(A\)掉,但懶惰的我並沒有去實踐,有興趣的可以試一試;

正解

這其實是一道經典的最短路的一種題型。

先跑一遍\(SPFA\),處理出\(dist\)數組;

然后再利用\(dist\)數組處理出每一條邊的\(maxdis\);

\(maxdis\)數組從小到大排序(結構體排序);

img

看完圖應該都懂了吧。

code:

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<cctype>
#include<queue>
#define ll long long
#define R register
#define N 800005
#define int long long
#define INF 9999999999999999LL
using namespace std;
template<typename T>inline void read(T &a){
    char c=getchar();T x=0,f=1;
    while(!isdigit(c)){if(c=='-')f=-1;c=getchar();}
    while(isdigit(c)){x=(x<<1)+(x<<3)+c-'0';c=getchar();}
    a=f*x;
}
ll n,m,c,tot,h[N],vis[N],pd[N],maxdis[N];
ll dist[N],sum,ans,maxsum;
struct bian{
	int u,v,w; 
}b[N];
struct node{
	int nex,to,dis;
}edge[N<<1];
struct MAX{
	int maxdis,id;
	friend bool operator < (const MAX &a,const MAX &b){
		return a.maxdis<b.maxdis;
	}
}md[N];
inline void add(R int u,R int v,R int w){
	edge[++tot].nex=h[u];
	edge[tot].to=v;
	edge[tot].dis=w;
	h[u]=tot;
}
inline void spfa(R int s){
	for(R int i=1;i<=n;i++)dist[i]=INF;
	queue<int> q;q.push(s);dist[s]=0;vis[s]=1;
	while(!q.empty()){
		R int x=q.front();q.pop();vis[x]=0;
		for(R int i=h[x];i;i=edge[i].nex){
			R int xx=edge[i].to;
			if(dist[xx]>dist[x]+edge[i].dis){
				dist[xx]=dist[x]+edge[i].dis;
				if(!vis[xx]){
					vis[xx]=1;
					q.push(xx);
				}
			}
		}	
	}
}
signed main(){ 
	freopen("vestige.in","r",stdin);
	freopen("vestige.out","w",stdout);
	read(n);read(m);read(c);
	for(R int i=1;i<=m;i++){
		read(b[i].u);read(b[i].v);read(b[i].w);
		add(b[i].u,b[i].v,b[i].w);add(b[i].v,b[i].u,b[i].w);sum+=b[i].w;
	}
	spfa(1);
	for(R int i=1;i<=m;i++)
		md[i].maxdis=max(dist[b[i].u],dist[b[i].v]),md[i].id=i;
	sort(md+1,md+1+m);
	ans=sum;
	for(R int i=1;i<=m;i++){
		sum-=b[md[i].id].w;
		ans=min(ans,1LL*md[i].maxdis*c+sum);
	}
	printf("%lld\n",ans);
    return 0;
}

T4 copy

描述

從前有一個$ {1, 2, \dots , n}$ 的排列,你希望用剪切/粘貼操作,將這個排列變成 \(1,2, \dots , n\)

一次剪切/粘貼操作指的是把序列中某段連續的子區間整體向前或者向后平移一段距離。由於你認為同時按 $Ctrl+X $很累手指,所以想要知道最少的操作次數。

輸入

輸入文件包含多組數據。

每組數據有兩行,其中第一行包含一個正整數 \(n\),第二行給出一組 \({1, 2, ..., n}\) 的排列。

以一個 \(0\) 來結束輸入。

輸出

對於每組數據,輸出一行一個整數,表示最小的操作次數。

輸入樣例 1:

6
2 4 1 5 3 6
5
3 4 5 1 2
0

輸出樣例 1 :

2
1

提示

對於 \(20 \%\) 的數據,數據組數 \(\leq 6\)

對於\(100\%\) 數據,\(n \leq 9\),數據組數 \(\leq 50\)

題解

\(IDA^*\)的一道很不錯的題

但是還是講一下粗略的講\(IDA^*\),(我覺得可能一些人並不了解);

一道入門題

\([SCOI2005]\)騎士精神為例,先講一下\(IDA^*\)的基礎前置知識;

突然加上一道題。。。

T4+ [SCOI2005]騎士精神

描述

 在一個\(5×5\)的棋盤上有\(12\)個白色的騎士和\(12\)個黑色的騎士, 且有一個空位。在任何時候一個騎士都能按照騎
士的走法(它可以走到和它橫坐標相差為\(1\),縱坐標相差為\(2\)或者橫坐標相差為\(2\),縱坐標相差為\(1\)的格子)移動到空
位上。 給定一個初始的棋盤,怎樣才能經過移動變成如下目標棋盤: 為了體現出騎士精神,他們必須以最少的步
數完成任務。

img

輸入

第一行有一個正整數\(T(T<=10)\),表示一共有\(N\)組數據。接下來有\(T\)\(5×5\)的矩陣,\(0\)表示白色騎士,\(1\)表示黑色騎 士,\(*\)表示空位。兩組數據之間沒有空行。

輸出

 對於每組數據都輸出一行。如果能在\(15\)步以內(包括\(15\)步)到達目標狀態,則輸出步數,否則輸出-\(1\)

輸入樣例 1:

2
10110
01*11
10111
01001
00000
01011
110*1
01110
01010
00100

輸出樣例1:

7
-1

題解

題意:給你一個初始棋盤,要求用最少的步數移動馬達到如上圖的目標狀態(要求棋盤中的馬只能走“日”)。

咱們先拋開\(IDA^*\),先如何優化爆搜;

這里的馬和象棋里的馬走法相同,但題目中要求讓馬走,但是要是馬的話,搜索分支比較多,所以我們要考慮讓空格走(很顯然吧)。

下面步入正題:

\(IDA^*\)就是帶有迭代加深和估價函數優化的搜索。

可能某些人對以上兩個名詞很陌生,下面一些前置知識可能會帶你透徹一下。

前置知識1:迭代加深
定義:

每次限定一個\(maxdep\)最大深度,使搜索樹的深度不超過\(maxdep\)

	for(R int maxdep=1;maxdep<=題目中給的最大步數;maxdep++){
		dfs(0,maxdep);//0為出入函數中當前步數,maxdep為傳入的最大深度。
		if(success)break;//如果搜索成功則會在dfs函數中將success賦值為1。
	}
使用范圍:

1.在有一定的限制條件時使用(例如本題中“如果能在\(15\)步以內(包括\(15\)步)到達目標狀態,則輸出步數,否則輸出\(-1\)。“)。

2.題目中說輸出所以解中的任何一組解。

為什么能夠降低時間復雜度:

我們可能會在一個沒有解(或解很深的地方無限遞歸然而題目中要求輸出任何的一組解),所以我們限制一個深度,讓它去遍歷更多的分支,去更廣泛地求解,(其實和\(BFS\)有異曲同工之妙)。

前置知識2:估價函數
定義:

\(f(n)=g(n)+h(n)\)

其中\(f(n)\)是節點的估價函數,\(g(n)\)是現在的實際步數,\(h(n)\)是對未來步數的最完美估價(“完美”的意思是可能你現實不可能實現,但你還要拿最優的步數去把\(h(n)\)算出來,可能不太好口胡,可以參考下面的實例)。

應用:
    void dfs(int dep,int maxdep){
        if(evaluate()+dep>maxdep)return;
        //evaluate函數為對未來估價的函數,若未來估價加實際步數>迭代加深的深度則return。
        if(!evaluate){
            success=1;
            printf("%d\n",dep);
            return;
        }
        ......
    }
前置知識3:\(A^*\)\(IDA^*\)的區別

\(A^*\)是用於對\(BFS\)的優化;

\(IDA^*\)是對結合迭代加深的\(DFS\) 的優化。

本質上只是在\(BFS\)\(DFS\)上加上了一個估價函數。

何時使用因題而定:

\(A^*\)[SCOI2007]k短路);\(IDA^*\)[SCOI2005]騎士精神UVA11212 Editing a Book 就是上面的兩道題)。

前置知識畢!!!

現在就是要想一個比較好的估價函數(若估價函數不好的話,優化效率就並不高,例如若估價函數一直為0,那就是爆搜)。

我們可以想一下,每次空白格子和黑白棋子交換,最優的情況就是每次都把黑白棋子移動到目標格子。

那么你的估價函數就出來了:

    const int goal[7][7]={
        {0,0,0,0,0,0},
        {0,1,1,1,1,1},
        {0,0,1,1,1,1},
        {0,0,0,2,1,1},
        {0,0,0,0,0,1},
        {0,0,0,0,0,0}
    };    
    inline int evaluate(){
        R int cnt=0;
        for(R int i=1;i<=5;i++)
            for(R int j=1;j<=5;j++)
                if(mp[i][j]!=goal[i][j])cnt++;
        return cnt;
    }

下面就是爆搜了:

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<cctype>
#define ll long long
#define R register
using namespace std;
template<typename T>inline void read(T &a){
    char c=getchar();T x=0,f=1;
    while(!isdigit(c)){if(c=='-')f=-1;c=getchar();}
    while(isdigit(c)){x=(x<<1)+(x<<3)+c-'0';c=getchar();}
    a=f*x;
}
int n,m,t,mp[7][7],stx,sty,success;
char ch;
const int dx[]={0,1,1,-1,-1,2,2,-2,-2};
const int dy[]={0,2,-2,2,-2,1,-1,1,-1};
const int goal[7][7]={
    {0,0,0,0,0,0},
    {0,1,1,1,1,1},
    {0,0,1,1,1,1},
    {0,0,0,2,1,1},
    {0,0,0,0,0,1},
    {0,0,0,0,0,0}
};
inline int evaluate(){
    R int cnt=0;
    for(R int i=1;i<=5;i++)
        for(R int j=1;j<=5;j++)
            if(mp[i][j]!=goal[i][j])cnt++;
    return cnt;
}
inline int safe(R int x,R int y){
    if(x<1||x>5||y<1||y>5)return 0;
    return 1;
}
inline void A_star(R int dep,R int x,R int y,R int maxdep){
    if(dep==maxdep){
        if(!evaluate())success=1;
        return;
    }
    for(R int i=1;i<=8;i++){
        R int xx=x+dx[i];
        R int yy=y+dy[i];
        if(!safe(xx,yy))continue;
        swap(mp[x][y],mp[xx][yy]);
        int eva=evaluate();
        if(eva+dep<=maxdep)
            A_star(dep+1,xx,yy,maxdep);
        swap(mp[x][y],mp[xx][yy]);//回溯
    }
}
int main(){
    read(t);
    while(t--){
        success=0;
        for(R int i=1;i<=5;i++){
            for(R int j=1;j<=5;j++){
                cin>>ch;
                if(ch=='*')mp[i][j]=2,stx=i,sty=j;//記錄起點即為空白格子
                else mp[i][j]=ch-'0';
            }
        }
        if(!evaluate()){printf("0\n");continue;}
        for(R int maxdep=1;maxdep<=15;maxdep++){
            A_star(0,stx,sty,maxdep);
            if(success){printf("%d\n",maxdep);goto ZAGER;}
        }
        printf("-1\n");
        ZAGER:;
    }
    return 0;
}
\(IDA^*\)入門鋪墊完成。。。終於到T4了
大致題意:給出一個1~n的排列,每次可以交換相鄰兩個區間,問最少移動幾次使其變成1,2,3...n。

本題若是想用\(IDA^*\)便是考察估價函數的設計(本題估價函數確實不好設計)。

若這個區間是連續的那么

畫一個圖想一下

code(沒有任何其他剪枝,不動任何的思維):

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<cctype>
#include<cmath> 
#define ll long long
#define R register
#define N 10
using namespace std;
template<typename T>inline void read(T &a){
    char c=getchar();T x=0,f=1;
    while(!isdigit(c)){if(c=='-')f=-1;c=getchar();}
    while(isdigit(c)){x=(x<<1)+(x<<3)+c-'0';c=getchar();}
    a=f*x;
}
int n,maxdep,num[N],tmp[N],success;
inline int gujia(){
	R int tot=0;
	for(R int i=1;i<=n-1;i++)
		if(num[i+1]!=num[i]+1) tot++;
	tot+=(num[n]!=n);
	return (tot+2)/3;
}
inline void ctrl_x(R int l,R int mid,R int r){
	R int now=l;
	for(R int i=mid+1;i<=r;i++)
		tmp[now++]=num[i];
	for(R int i=l;i<=mid;i++)
		tmp[now++]=num[i];
	for(R int i=l;i<=r;i++)
		num[i]=tmp[i];
}
inline void dfs(R int dep){
	if(success)return;
	R int zager=gujia();
	if(zager+dep>maxdep)return;
	if(!zager){
		printf("%d\n",dep);
		success=1;return;
	}
	for(R int l=1;l<=n-1;l++){
		for(R int mid=l;mid<=n-1;mid++){
			for(R int r=mid+1;r<=n;r++){
				ctrl_x(l,mid,r);
				dfs(dep+1);
				if(success)return;
				ctrl_x(l,l+r-mid-1,r);
			}
		}
	}
}
inline void IDAstar(){
	success=0;
	for(maxdep=1;maxdep;maxdep++){
		dfs(0);
		if(success)break;
	}
}
int main(){
	freopen("copy.in","r",stdin);
	freopen("copy.out","w",stdout);
	while(233){
		read(n);
		if(!n)break;
		for(R int i=1;i<=n;i++)read(num[i]);
		IDAstar();
	}
    return 0;
}

T5 trainfair

描述

\(MS\) 國有 \(N\) 座城市,編號從 \(1\)\(N\) ,其中 \(1\) 號城市是 \(MS\) 國的首都。

\(MS\) 國里,只有一家鐵路公司,經營着 \(M\) 條連接着各城市的鐵路線路,每條線路都雙向地連接着兩座不同的城市。通過這些線路,我們可以在任意兩座城市間通行。

原本,乘坐一條線路只需要花費 \(1\) 角錢。可是由於經營不善,鐵路公司提出計划,要在今后的 \(Q\) 年間,每年給某一條線路漲價為 \(2\) 角錢。保證一條線路不會被漲價多次。

另外,這個鐵路公司每年都會在每一座城市進行一次居民滿意度調查。原本每一座城市中的居民都對鐵路公司的服務十分滿意,但在線路漲價后就說不定了。每一年的滿意度調查都會在當年的漲價計划實施后進行。如果一座城市中的居民在當年坐火車去首都所需的最少花費相比於計划提出前增加了,他們就會對鐵路公司抱有不滿。首都的居民永遠不會對鐵路公司抱有不滿。

在計划施行前,你需要幫助鐵路公司統計出未來的每一年中,各將有多少城市的居民對鐵路公司抱有不滿。

輸入

輸入文件第一行包含三個正整數 \(N\), \(M\), \(Q\),分別表示 \(MS\) 國的城市數量、鐵路路線數量和漲價計划將要實施的時間長度。

接下來 \(M\) 行,其中第 \(i\) 行包含 \(2\) 個整數 \(U_i, V_i\),表示第 \(i\) 條路線連接着編號為 \(U_i\)\(V_i\) 的兩座城市。

接下來 \(Q\) 行,其中第 \(j\) 行包含一個整數 \(R_j\),表示計划施行后第 \(j\) 年將會讓第 \(R_j\) 條線路漲價。

輸出

輸出 \(Q\) 行,其中第 \(j\) 行表示第 \(j\) 年居民對鐵路公司抱有不滿的城市的數量。

輸入樣例 1 :

5 6 5
1 2
1 3
4 2
3 2
2 5
5 3
5
2
4
1
3

輸出樣例 1

0
2
2
4
4

提示

對於 \(24\%\) 的測試數據,\(N \leq 100, M \leq 4950, Q \leq 30\)

對於 \(48\%\) 的測試數據,\(Q \leq 30\)

對於另 \(32\%\) 的測試數據,正確的輸出中不同的數字不超過\(50\) 種。

對於 \(100\%\) 的測試數據,滿足以下條件:

\(2 \leq N \leq 10^5, 1 \leq Q \leq M \leq 2 \times 10^5\),

\(1 \leq U_i, V_i \leq N, U_i ≠ V_i,1 \leq R_j \leq M\),且兩個城市間直接連接的路線不超過一條。

題解

大致題意:\(N\)個點\(M\)條邊,初始邊權均為1,每次修改一條邊的權值,問每次修改后有幾個點的到1的最短路與初始最短路的長度不同。
我的暴力思路(60分)

首先\(48\%\)數據的可以發現\(Q\)很小,使用暴力修改(其實是\(O(1)\))暴力\(Q\)\(SPFA\),每次暴力統計\(ans\)

為什么能過\(60\%\)的數據呢(好像是數據水)

我們可以想一下,假如我們修改過一條邊,那么下次再次修改的時候就已經沒有貢獻了,所以可以用\(used\)數組標記,如果標記過直接輸出上次的\(ans\)

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<cctype>
#include<queue>
#include<vector>
#define ll long long
#define R register
#define INF 0x3f3f3f3f
#define N 200005
using namespace std;
template<typename T>inline void read(T &a){
    char c=getchar();T x=0,f=1;
    while(!isdigit(c)){if(c=='-')f=-1;c=getchar();}
    while(isdigit(c)){x=(x<<1)+(x<<3)+c-'0';c=getchar();}
    a=f*x;
}
int n,m,q,u,v,tot,x,ans;
int h[N],used[N],id[N],dist[N],vis[N],last[N];
struct node{
	int nex,to,dis;
}edge[N<<1];
inline void add(R int u,R int v,R int w){
	edge[++tot].nex=h[u];
	edge[tot].to=v;
	edge[tot].dis=w;
	h[u]=tot;
}
inline void spfa(R int s){
	queue<int> q;
	for(R int i=1;i<=n;i++)dist[i]=INF,vis[i]=0;
	q.push(s);vis[s]=1;dist[s]=0;
	while(!q.empty()){
		R int x=q.front();q.pop();vis[x]=0;
		for(R int i=h[x];i;i=edge[i].nex){
			R int xx=edge[i].to;
			if(dist[xx]>dist[x]+edge[i].dis){
				dist[xx]=dist[x]+edge[i].dis;
				if(!vis[xx]){
					vis[xx]=1;q.push(xx);
				}
			}
		}
	}
}
int main(){
	freopen("trainfair.in","r",stdin);
	freopen("trainfair.out","w",stdout);
	read(n);read(m);read(q);
	for(R int i=1;i<=m;i++){
		read(u);read(v);
		add(u,v,1);add(v,u,1);
		id[i]=tot;//記錄邊的序號
	}
	spfa(1);
	for(R int i=1;i<=n;i++)last[i]=dist[i];//初始最短路的值
	for(R int i=1;i<=q;i++){
		read(x);
		edge[id[x]].dis+=2;
		edge[id[x]-1].dis+=2;//修改
		if(used[x])printf("%d\n",ans);//標記
		else{
			spfa(1);ans=0;//暴力SPFA
			for(R int j=1;j<=n;j++)
				if(dist[j]!=last[j])ans++;//暴力統計ans
			printf("%d\n",ans);
			used[x]=1;
		}
	}
	fclose(stdin);
	fclose(stdout);
    return 0;
}

正解

正解和上面的暴力思路可以說是沒有多少相似的地方。。。

大致思路:
  1. 在原圖上跑一邊\(SPFA\)(或\(Dij\)堆優化),目的是處理出最短路數組\(dist\)

  2. 重新建一個關於最短路的拓撲圖,即如果滿足最短路\(dist[xx]==dist[x]+edge[i].dis\)的性質則建邊,由於我們在原圖從\(1\)開始跑,而我們要的拓撲圖是要從每個點連到\(1\),所以要反向建邊,並且統計每個點的出度。

    inline void bfs(R int x){
    	for(R int x=1;x<=n;++x){
    		for(R int i=h[x];i;i=edge[i].nex){
    			R int xx=edge[i].to;
    			if(dist[xx]==dist[x]+edge[i].dis)
    				ADD(xx,x,edge[i].dis,edge[i].id);chu[xx]++;nxt[i/2]=xx;
    		}
    	} 
    }
    
  3. 下面就是核心的

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<cctype>
#include<queue>
#include<vector>
#define ll long long
#define R register
#define INF 0x3f3f3f3f
#define N 200005
using namespace std;
template<typename T>inline void read(T &a){
    char c=getchar();T x=0,f=1;
    while(!isdigit(c)){if(c=='-')f=-1;c=getchar();}
    while(isdigit(c)){x=(x<<1)+(x<<3)+c-'0';c=getchar();}
    a=f*x;
}
int n,m,q,u,v,tot=1,opt,ans,t=1;
int h[N],used[N],dist[N],vis[N],head[N],chu[N],nxt[N];
struct node{
	int nex,to,dis,id;
}edge[N<<1],e[N<<1];
inline void ADD(R int u,R int v,R int w,R int id){
	e[++t].nex=head[u];
	e[t].to=v;
	e[t].dis=w;
	e[t].id=id; 
	head[u]=t;
}
inline void add(R int u,R int v,R int w,R int id){
	edge[++tot].nex=h[u];
	edge[tot].to=v;
	edge[tot].dis=w;
	edge[tot].id=id; 
	h[u]=tot;
}
inline void spfa(R int s){
	queue<int> q;
	for(R int i=1;i<=n;i++)dist[i]=INF;
	q.push(s);vis[s]=1;dist[s]=0;
	while(!q.empty()){
		R int x=q.front();q.pop();vis[x]=0;
		for(R int i=h[x];i;i=edge[i].nex){
			R int xx=edge[i].to;
			if(dist[xx]>dist[x]+edge[i].dis){
				dist[xx]=dist[x]+edge[i].dis;
				if(!vis[xx]){
					vis[xx]=1;q.push(xx);
				}
			}
		}
	}
}
inline void bfs(R int x){
	for(R int x=1;x<=n;++x){
		for(R int i=h[x];i;i=edge[i].nex){
			R int xx=edge[i].to;
			if(dist[xx]==dist[x]+edge[i].dis){
				ADD(xx,x,edge[i].dis,edge[i].id);chu[xx]++;nxt[i/2]=xx;
			}
		}
	} 
}
queue<int> Q;
int main(){
	freopen("trainfair.in","r",stdin);
	freopen("trainfair.out","w",stdout);
	read(n);read(m);read(q);
	for(R int i=1;i<=m;i++){
		read(u);read(v);
		add(u,v,1,i);add(v,u,1,i);
	}
	spfa(1);
	bfs(1);tot=1; 
	memset(h,0,sizeof(h));
	for(R int i=1;i<=n;i++){
		for(R int j=head[i];j;j=e[j].nex){
			R int xx=e[j].to;
			add(xx,i,e[j].dis,e[j].id); 
		}
	}	
	while(q--){
		read(opt);R int x=nxt[opt];
		if(used[opt]||x==0){printf("%d\n",ans);continue;}
		used[opt]=1;chu[x]--;
		if(!chu[x]){
			Q.push(x);
			while(!Q.empty()){
				R int u=Q.front();Q.pop();ans++;
				for(R int i=h[u];i;i=edge[i].nex){
					R int v=edge[i].to;
					if(used[edge[i].id])continue;
					used[edge[i].id]=1;
					chu[v]--;if(!chu[v])Q.push(v);
				}
			}
		}
		printf("%d\n",ans);
	}
	fclose(stdin);
	fclose(stdout);
    return 0;
}

未完待續......


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM