noip模擬賽#39

昨晚打開的題想了一會發現都不會後決定慢慢想。而後早上開校會的時候莫名其妙的都想出來了。。。git

T1:m=100,ai=50000,i<=5。1到m的數每一個數只能用一次,判斷是否可以有這些數的某些數相乘獲得ai算法

=>啊O(mai)的dp?利用質因數分解來寫?不會。不會。直到早上以爲數據範圍那麼小寫個爆搜+剪枝應該能過吧,畢竟是T1。並且50000的話每一個數的因數都挺少的。app

正解:優化

預處理出剩下前k張卡片時對於每一個質因數最多能夠貢獻多少次冪。若搜索到一個狀態,就算剩下的卡片全部均可以使用且不考慮分配原則的狀況下仍沒法約去全部質因數,則一定無解,能夠進行剪枝操做。再配合算法一,指望得分100%。spa

#include<cstdio>
#include<cstring>
#include<cctype>
#include<algorithm>
using namespace std;
#define rep(i,s,t) for(int i=s;i<=t;i++)
#define dwn(i,s,t) for(int i=s;i>=t;i--)
#define clr(x,c) memset(x,c,sizeof(x))
int read(){
	int x=0;char c=getchar();
	while(!isdigit(c)) c=getchar();
	while(isdigit(c)) x=x*10+c-'0',c=getchar();
	return x;
} 
const int nmax=1e3+5;
const int inf=0x7f7f7f7f;
int a[10][nmax],t[10],n,m;
bool vis[nmax];
bool dfs(int x,int pre,int now){
	if(x>n) return 1;
	int td=t[x]/now;
	if(td<=a[x][pre]) return 0;
	if(td<=m&&!vis[td]) {
		vis[td]=1;
		if(dfs(x+1,0,1)) return 1;
		vis[td]=0;
	}
	rep(i,pre+1,a[x][0]){
		if(td%a[x][i]==0&&!vis[a[x][i]]) {
			vis[a[x][i]]=1;
			if(dfs(x,i,now*a[x][i])) return 1;
			vis[a[x][i]]=0;
		}
	}
	return 0;
}
int main(){
	freopen("cards.in","r",stdin);freopen("cards.out","w",stdout);
	int T=read();
	rep(_T,1,T){
		clr(vis,0);
		n=read(),m=read();
		rep(i,1,n) t[i]=read();
		rep(i,1,n) a[i][0]=0;
		rep(i,1,n) rep(j,2,m) if(t[i]%j==0) a[i][++a[i][0]]=j;
		if(!dfs(1,0,1)) puts("Yes");else puts("No");
	}
	fclose(stdin);fclose(stdout);
	return 0;
}
/*
4
2 3
2 3
2 3
3 6
2 5
4 6
2 7
24 30
*/

T2:給出一個n*m的矩陣。矩陣中的數表示高度。給出初始水的高度和出水口。求出每一個點最後的高度。blog

=>嗯矩陣這鐵定應該是亂搞+bfs。。而後我就想着亂搞啊亂搞啊。。死定了。。而後在一個地方卡住了,有可能有不少條路徑能夠走啊這bfs的順序根本沒辦法保證啊這。。。而後忽然發現這不是最短路嗎?個人思惟能力仍是太弱了。。。可是終於想出來了happy。spfa新寫法被坑了很久。。get

=>裸的spfa只能70分。正解要加個SLF優化。。。不會寫。。。string

正解:it

對於某個點,設T表示流完水後,此點的高度(若是上面沒水,就是它自己的高度,不然就是水的高度)。同時,T又能夠表示此點到源點的路徑上,通過的最小的T的值,毫無疑問,咱們就是須要讓每一個點的T最小。而且,若是某點的T表示的是水的高度,他就能夠被他周圍的,比他小的T更新。io

最後每一個點的答案就是他的T減他自己的高度。指望得分70%。

SPFA加入經典的SLF優化,就能夠經過100%的數據了。

#include<cstdio>
#include<cstring>
#include<cctype>
#include<algorithm>
#include<queue>
using namespace std;
#define rep(i,s,t) for(int i=s;i<=t;i++)
#define dwn(i,s,t) for(int i=s;i>=t;i--)
#define clr(x,c) memset(x,c,sizeof(x))
int read(){
	int x=0;char c=getchar();
	while(!isdigit(c)) c=getchar();
	while(isdigit(c)) x=x*10+c-'0',c=getchar();
	return x;
}
const int nmax=805;
const int inf=0x7f7f7f7f;
int dist[nmax][nmax],a[nmax][nmax],mp[nmax][nmax];
int xx[4]={0,0,1,-1};
int yy[4]={1,-1,0,0};
int q[16777216][2];bool inq[nmax][nmax];
int main(){
	freopen("shower.in","r",stdin);freopen("shower.out","w",stdout);
	int n=read(),m=read(),h=read();
	rep(i,1,n) rep(j,1,m) a[i][j]=read();
	int sa=read(),sb=read();
	rep(i,1,n) rep(j,1,m) mp[i][j]=max(a[i][j]-a[sa][sb],0);
	if(h<a[sa][sb]) {
		rep(i,1,n){
			rep(j,1,m) printf("%d ",max(h-a[i][j],0));printf("\n");
		}
		return 0;
	}
	clr(dist,0x7f);dist[sa][sb]=0;
	int ql=0,qr=1,x,y,tx,ty,td;q[qr][0]=sa;q[qr][1]=sb;
	while(ql!=qr){ //這裏不能是ql<qr!!!!!!! 
		ql=((ql+1)&1048575);x=q[ql][0];y=q[ql][1];inq[x][y]=0;
		rep(i,0,3){
			tx=x+xx[i];ty=y+yy[i];
			if(tx<1||ty<1||tx>n||ty>m) continue;
			td=max(dist[x][y],mp[tx][ty]);
			if(dist[tx][ty]>td){
				dist[tx][ty]=td;
				if(!inq[tx][ty]){
					inq[tx][ty]=1;qr=((qr+1)&1048575);
					q[qr][0]=tx;q[qr][1]=ty;
				}
			}
		}
	}
	rep(i,1,n){
		rep(j,1,m) printf("%d ",dist[i][j]+a[sa][sb]-a[i][j]);printf("\n");
	}
	fclose(stdin);fclose(stdout);
	return 0;
} 
/*
3 3 66
6 9 1
7 8 1
6 8 1
3 2
*/

T3:給出兩顆樹,在兩棵樹的任兩個點連邊能夠造成一棵樹。求n*m種樹的最長路徑。

=>很明顯應該先求出原樹中每一個點的最長路徑。這個樹形dp一下能夠解決。而後接下來就是有多是原樹中的路徑或是通過新加入的邊的路徑。那麼sort一下而後二分找出那麼臨界點就能夠了。複雜度O(nlogn)。ps:其實nm1e5根本不用sort好不TAT。。。。

正解:

         明顯地,若是咱們肯定了i點(不妨設i在N個點的樹上),要在另外一棵樹上找到j點使得新生成的最長鏈是直徑,則只須要知足f(j)>len-1-f(i),如此可使用桶維護s(x)表示有多少f(j)是小於等於x的,那麼肯定了i後,f(i)會對答案貢獻M-s(len-1-f(i))次,自己兩棵樹上的較長直徑會對答案貢獻s(len-1-f(i))次。

         對於快速地求出全部的f(i)咱們能夠選擇樹形DP後旋根。

         在樹形DP內咱們能夠記f(i)爲i到以i節點爲根的子樹下任意節點的最長鏈。G(i)爲與f(i)不相交的次長鏈。轉移的時候只須要把i的全部兒子的f值+1中最大的賦值給f(i),次大的(不須要嚴格比f(i)小)賦值給g(i)。

         而後咱們就知道了根節點到樹上任意一點的最長鏈。樹上的每一個點中MAX{g(i)+f(i)}便是直徑。

         而後咱們須要作的是旋根操做。

         若是咱們已經知道了根i的f值,而且須要把根轉給兒子j。則須要把j對i的f值影響刪去再把i看成j的兒子,把i的f值+1與j的f值g值比較。而後就知道了j做爲根時的f值與g值(變換後i的g值多是錯誤的,可是不影響旋根,因此不須要特意維護)。

就這樣遍歷整顆樹一遍,每一個點在其自己的樹上能夠延伸的最長鏈f(i)的長度就能夠算出來了。

         時間複雜度爲O(N+M)指望得分100%。

#include<cstdio>
#include<cstring>
#include<cctype>
#include<algorithm>
using namespace std;
#define rep(i,s,t) for(int i=s;i<=t;i++)
#define dwn(i,s,t) for(int i=s;i>=t;i--)
#define clr(x,c) memset(x,c,sizeof(x))
#define qwq(x) for(edge *o=head[x];o;o=o->next)
#define ll long long
int read(){
	int x=0;char c=getchar();
	while(!isdigit(c)) c=getchar();
	while(isdigit(c)) x=x*10+c-'0',c=getchar();
	return x;
} 
const int nmax=2e5+5;
const int inf=0x7f7f7f7f;
int f[nmax],g[nmax];ll sm[nmax];
struct edge{
	int to;edge *next;
};edge es[nmax<<1],*pt=es,*head[nmax];
void add(int u,int v){
	pt->to=v;pt->next=head[u];head[u]=pt++;
	pt->to=u;pt->next=head[v];head[v]=pt++;
}
void dfs(int x,int fa){
	qwq(x) if(o->to!=fa){
		dfs(o->to,x);
		f[x]=max(f[x],f[o->to]+1);
	}
}
void DFS(int x,int fa){
	//printf("(%d %d)\n",x,fa);
	int ta=-1,tb=-1,ca=0;
	qwq(x) if(o->to!=fa) {
		if(f[o->to]>ta) tb=ta,ta=f[o->to],ca=o->to;
		else if(f[o->to]>tb) tb=f[o->to];
	}
	if(ta!=-1&&tb!=-1) ta+=2,tb+=2;
	else if(ta<0&&tb<0) ta=0;
	else tb=0;
	qwq(x) if(o->to!=fa){
		if(o->to!=ca) g[o->to]=max(g[x]+1,ta);
		else g[o->to]=max(g[x]+1,tb);
	}
	qwq(x) if(o->to!=fa) DFS(o->to,x);
}
int find(int x,int r){
	int l=0,ans=0,mid;
	while(l<=r){
		mid=(l+r)>>1;
		if(f[mid]<=x) ans=mid,l=mid+1;
		else r=mid-1;
	}
	return ans;
}
//二分的邊界什麼的老是寫錯。我開始是弄成g[0]=inf。而後f[mid]>=x 判斷。。。而後Wa了三個點。要注意一下。 
int main(){
	freopen("connect.in","r",stdin);freopen("connect.out","w",stdout);
	int n=read(),m=read(),u,v;
	rep(i,1,n-1) u=read(),v=read(),add(u,v);
	rep(i,1,m-1) u=read()+n,v=read()+n,add(u,v);
	dfs(1,0);dfs(n+1,0);DFS(1,0);DFS(n+1,0);
	
	rep(i,1,n+m) g[i]=max(g[i],f[i]);
	rep(i,1,m) f[i]=g[i+n];
	sort(g+1,g+n+1);sort(f+1,f+m+1);
	rep(i,1,m) sm[i]=sm[i-1]+f[i];
	
	int mx=max(g[n],f[m]),cur;ll ans=0;
	rep(i,1,n) {
		cur=find(mx-g[i]-1,m);
		ans+=1ll*cur*mx+1ll*(m-cur)*(g[i]+1)+sm[m]-sm[cur];
	}
	printf("%lld\n",ans);
	fclose(stdin);fclose(stdout);
	return 0;
}
/*
3 5
1 2
1 3
1 2
1 3
3 4
4 5
4+8+14=26

*/
相關文章
相關標籤/搜索