第八次ACM訓練(Saturday)

A題順序

13:00 比賽開始
14:13 a dxw
14:32 j yl
14:47 c dxw
15:59 g yl
16:05 d zjl
17:32 h dxw

總結

第八次訓練沒什麼特別大的感觸,只意識到自己應該多收集些板子,免得再遇到一些模板題半天下不了手

A - Blank (dp)

description

有n個點,每個點可以放0,1,2,3四種數,現在給出m個限制[l,r,x]要求[l,r]內只有x種數,問方案數(n,m<=100)

solution

本以爲是簽到題,一個狀壓走起,打完覺着不太對,怎麼樣例過不了呀……差點把自己給送了
直接狀壓是不行的,因爲會重複考慮情況
我們設f[i][x][y][z][k]表示前i個數,0-3最後分別位於x,y,z,k位置,但顯然有一個數必須是i,所以去掉一維,同時我們考慮剩下的數的位置從大到小排即(x>y>z),那麼暴力枚舉更新即可

code

#include<bits/stdc++.h>
#define ll long long
#define max(a,b) ((a>b)?a:b)
#define min(a,b) ((a<b)?a:b)
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define rp(i,a,b) for(int i=a;i>=b;i--)
using namespace std; 
const int maxn=1e2+5;
const ll mo=998244353;
struct code{
	int l,r,x,y;
}a[maxn];
int n,m,i,t,j,k,l,x,y,z,p,T,r,mid,q,num;
ll f[2][maxn][maxn][maxn],ans; 
bool cmp(code x,code y){
	return x.r<y.r;
}
void make(int i,int x,int y,int z){
	f[1-p][x][y][z]%=mo;
	f[p][i][y][z]+=f[1-p][x][y][z];
	f[p][i][x][z]+=f[1-p][x][y][z];
	f[p][i][x][y]+=f[1-p][x][y][z];
	f[p][x][y][z]+=f[1-p][x][y][z];
}
int main(){
	//freopen("data.in","r",stdin);
	//freopen("data.out","w",stdout);
	scanf("%d",&T);q=1;
	while (T--){
		scanf("%d%d",&n,&m);
		fo(i,1,m)scanf("%d%d%d",&a[i].l,&a[i].r,&a[i].x);
		sort(a+1,a+m+1,cmp); 
		fo(j,0,3)f[0][0][0][0]=1;
		p=0;num=1;a[m+1].r=n+1;ans=0;
		fo(i,0,n){
			p=1-p;
			fo(x,0,max(i,1)-1)
				fo(y,0,max(x,1)-1)
					fo(z,0,max(y,1)-1){
						if (!f[1-p][x][y][z]) continue;
						t=0;
						for (l=num;a[l].r==i && !t && l<=m;l++){
							q=1;
							if (x>=a[l].l) q++;
							if (y>=a[l].l) q++;
							if (z>=a[l].l) q++;
							if (q!=a[l].x) t=1;
						}
						if (!t){
							if (i==n)ans+=f[1-p][x][y][z]%mo;
							else make(i,x,y,z);	
						}
						f[1-p][x][y][z]=0;
					} 
			while (a[num].r==i && num<=m) num++;
		}
		printf("%lld\n",ans%mo);
	} 
}

C - Vacation (結論題?)

description

給你0-n輛車的長度li、車頭距離終點si、最大速度vi,保證si≥si+1+li+1,每輛車在遇到前面有車之前一直會已最大速度行駛,問0號車車頭到達終點的時間

solution

以後拷上一題的初始化一定要去LL和重新設置數據範圍
假如只有一輛車,時間是s0/v0
假如有兩輛車,有兩種情況:1、自己奇慢s0/v0 2、前面的大兄弟奇慢,自己追上後還得以它的龜速走,過線後再過一個它的車長,無論自己跑多快,最後還是得以前一輛車的節奏來(s1+l1)/v1
……(你是否發現了什麼?)
我們取max((sj+i=0jlj)/vj)max((sj+\sum_{i=0}^{j}lj)/vj)即可

code

#include<bits/stdc++.h>
#define ll long long
#define max(a,b) ((a>b)?a:b)
#define min(a,b) ((a<b)?a:b)
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define rp(i,a,b) for(int i=a;i>=b;i--)
using namespace std; 
const int maxn=1e6+5;
const ll mo=998244353;
struct code{
	int s,v,l;
}a[maxn];
int n,m,i,t,j,k,l,p,T,r,mid,q,num;
double x,y,z;
int read(){
	int f=1,s=0;char c=getchar();
	for(;c<'0'||c>'9';c=getchar())if(c=='-')f=-1;
	for(;c>='0'&&c<='9';c=getchar())s=s*10+c-'0';
	return f*s;
}
bool cmp(code x,code y){
	return x.s<y.s;
}
int main(){
	//freopen("data.in","r",stdin);
	//freopen("data.out","w",stdout);
	while (scanf("%d",&n)!=EOF){
		fo(i,0,n)a[i].l=read();
		fo(i,0,n)a[i].s=read();
		fo(i,0,n)a[i].v=read();
		double t=a[0].s*1.0/a[0].v,sum=0;
		fo(i,1,n){
			sum+=a[i].l;
			t=max(t,(sum+a[i].s)*1.0/a[i].v);
		}
		printf("%.10lf\n",t);
	}
}

D - Path (spfa+dinic)

description

給定一個有向圖,問怎樣刪除一些邊,使得最後1-n的最短路變大,且刪除邊的總和儘量小

solution

構建出一個最短路的DAG,跑一遍最小割

code

#include<bits/stdc++.h>
#define ll long long
#define fo(i,a,b) for(ll i=a;i<=b;i++)
#define rp(t,a) for(ll t=first[a];t;t=nex[t])
using namespace std;
const ll maxn=2e4+5;
ll first[maxn],last[maxn],nex[maxn],value[maxn];
ll v[maxn],bz[maxn],dui[maxn],d[maxn];
ll n,m,i,t,j,k,l,x,y,z,num,T,ans;
void lian(ll x,ll y,ll z){
	last[++num]=y;nex[num]=first[x];first[x]=num;value[num]=z;
}
void spfa(ll s){
	memset(d,127,sizeof(d));d[s]=0;
	ll i=0,j=1;v[j]=s;bz[s]=1;
	while(i<j){
		x=v[++i];
		rp(t,x){
			if (d[last[t]]<=d[x]+value[t])continue;
			d[last[t]]=d[x]+value[t];
			if (!bz[last[t]]) bz[v[++j]=last[t]]=1;
		}
		bz[x]=0;
	}
}
ll bfs(){
	memset(d,0,sizeof(d));d[1]=1;
	ll i=0,j=1;v[j]=1;
	while(i<j){
		x=v[++i];
		rp(t,x)
			if (value[t] && !d[last[t]]) d[v[++j]=last[t]]=d[x]+1;
	}
	return d[n];
}
ll dg(ll x,ll sum){
	if (x==n) return sum;
	ll p=sum,k;
	rp(t,x){
		if (!value[t]||d[last[t]]!=d[x]+1)continue;
		k=dg(last[t],min(p,value[t]));
		if (k){
			value[t]-=k;
			value[dui[t]]+=k;
			p-=k;
			if (!p) break;
		}
	}
	if (p==sum) d[x]=-1;
	return sum-p;
}
int main(){
	//freopen("data.in","r",stdin);
	scanf("%lld",&T);
	while (T--){
		scanf("%lld%lld",&n,&m);
		num=0;
		memset(first,0,sizeof(first));
		fo(i,1,m)scanf("%lld%lld%lld",&x,&y,&z),lian(x,y,z);
		spfa(1);
		//make DAG
		fo(i,1,n)
			rp(t,i)
				if (d[i]+value[t]!=d[last[t]]) value[t]=0;
				else lian(last[t],i,0),dui[num]=t,dui[t]=num;
		//SAP
		ans=0;
		while (bfs())ans+=dg(1,2e14);
		printf("%lld\n",ans);
	}
} 

G - String(貪心)

description
給定一個僅包含小寫字母的字符串
從中選取出一個長度爲k的子序列
輸出字典序最小的子序列
不過子序列做出一定的限制:每個字母至少出現L[i]次至多出現R[i]次
solution
很顯然我們可以貪心地想 每個位置選擇符合條件的最小的字母
於是乎 問題的重點在於判斷 這個位置填這個字母合不合適

經過了一段時間的思考(和提交的WA)可以發現有幾種情況是不合適的:

  • 所有字母全部填上限次也填不滿剩餘的空位
  • 所有字母全部填下線次也超過剩餘的空位
  • 這個字母在這個位置之後沒有了
  • 這個字母已經填過R次了
  • 如果這個位置填了這個字母,剩餘的其他字母就算都填下限次也會超過剩餘的空位

emmm可能有一些條件是多餘的
不過並沒有進行嘗試
所以還是全部列出來

code

#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#include<algorithm>

typedef long long LL;
const int oo = 0x3f3f3f3f;
const int N = 1e5 + 10;
const int MOD = 1e9 + 7;
using namespace std;
int read() {
	int f = 1, s = 0; char c = getchar();
	for (; c < '0' || c>'9'; c = getchar())if (c == '-')f = -1;
	for (; c >= '0' && c <= '9'; c = getchar())s = s * 10 + c - '0';
	return f * s;
}
char s[N];
int f[N][26], ans_[N];
int l[26], r[26];
queue<int>q[26];
bool check(int x, int left) {
	if (q[x].empty())return false;
	if (r[x] == 0)return false;
	int most = 0, least = 0;
	for (int i = 0; i < 26; i++)
		if (f[q[x].front()][i] < l[i])return false;
		else {
			most += min(r[i], f[q[x].front()][i]);
			least += l[i];
		}
	if (most < left)return false;
	if (least > left)return false;
	if (left - 1 < least - l[x])return false;
	return true;
}
void work() {
	int n = strlen(s + 1);
	for (int i = 0; i < 26; i++)f[n + 1][i] = 0;
	for (int i = n; i >= 1; i--) {
		for (int j = 0; j < 26; j++)
			f[i][j] = f[i + 1][j];
		f[i][s[i] - 'a']++;
	}
	int k = read();

	for (int i = 0; i < 26; i++) {
		l[i] = read(); r[i] = read();
		while (q[i].empty() == false)q[i].pop();
	}
	for (int i = 1; i <= n; i++)q[s[i] - 'a'].push(i);
//printf("\n");
	int ans;
	for (ans = 0; ans < k; ans++) {
		bool flag = true;
		for (int i = 0; i < 26; i++)
			if (check(i, k - ans ) == true) {
				ans_[ans] = q[i].front();
				q[i].pop();
				if (l[i])l[i]--;
				if(r[i])r[i]--;
				flag = false;
				break;
			}
	//	printf("%d %d\n", ans, ans_[ans]);
		if (flag)break;
		for (int i = 0; i < 26; i++)
			while (q[i].empty() == false && q[i].front() < ans_[ans])
				q[i].pop();
	}
	if (ans < k - 1)printf("-1");
	else for (int i = 0; i < k; i++)putchar(s[ans_[i]]);
	printf("\n");
}
int main() {
	while (scanf("%s", s + 1) != EOF)work();
	return 0;
}

H - Kingdom (記憶化搜索+?)

description

給你一棵樹的殘缺的前序遍歷和中序遍歷(缺的數用0表示),問有多少種樹的前序遍歷和中序遍歷滿足條件

solution

前序遍歷好啊,直接告訴你根是什麼,所以我們主要根據前序遍歷來搜索,設前序遍歷a[i],中序遍歷b[i]
設狀態f(l,r,x,y)表示當前是前序遍歷[l,r]對應中序遍歷的[x,y],我們對a[l]的情況分類討論:

  • 假如現在a[l]存在且[x,y]中存在a[l](設在b中位置p),那我們直接就分成f(l+1,l+p-x,x,p-1)和f(l+p-x+1,r,p+1,y)
  • 假如現在a[l]存在但[x,y]不存在與之對應的數,那我們就考慮在[x,y]中找一個滿足遍歷要求的0和他配對
  • 假如a[l]不存在,那在[x,y]中隨便一個滿足遍歷要求且在[l,r]無配對的點都可以和他配對

還有一個問題,0和0的配對的方案數,在統計在兩個遍歷[l,r][x,y]中都沒有出現過的數的個數f[l][r][x],當按照(l+1,l+p-x,x,p-1)的方式分割時,相當於把f[l][r][x]個數選出f[l+1][l+p-x][x]個出來放到左子樹,因此要乘方案數Cf[l][r][x]f[l+1][l+px][x]C_{f[l][r][x]}^{f[l+1][l+p-x][x]},其他同理。
由於一個區間算過一次就不用再算了,因此我們可以記憶化一下,設爲g[l][r][x]。

code

#include<bits/stdc++.h>
#define ll long long
#define max(a,b) ((a>b)?a:b)
#define min(a,b) ((a<b)?a:b)
#define fo(i,a,b) for(ll i=a;i<=b;i++)
#define rp(i,a,b) for(ll i=a;i>=b;i--)
using namespace std; 
const ll maxn=1e2+5;
const ll mo=998244353;
ll a[maxn],b[maxn],c[maxn],d[maxn],bz[maxn],f[maxn][maxn][maxn];
ll c1[maxn][maxn],g[maxn][maxn][maxn];
ll n,m,i,t,j,k,l,p,T,r,mid,q,num;
ll dg(ll l,ll r,ll x,ll y){
	if (r<=l) return 1;
	if (g[l][r][x]>=0) return g[l][r][x];
	ll t,k,k1,num;t=k=k1=0;
	memset(bz,0,sizeof(bz));num=f[l][r][x];
	if (c[a[l]]){
		fo(i,x,y){
			if (k1<i && k<=i-x+l && i==c[a[l]])t=c1[num][f[l+1][i-x+l][x]]*dg(l+1,i-x+l,x,i-1)%mo*dg(i-x+l+1,r,i+1,y)%mo;
			k=max(k,d[b[i]]);
			k1=max(k1,c[a[l+1+i-x]]);
		}
	}else if (a[l]){
		fo(i,x,y){
			if (k1<i && k<=i-x+l && !b[i])t+=c1[num][f[l+1][i-x+l][x]]*dg(l+1,i-x+l,x,i-1)%mo*dg(i-x+l+1,r,i+1,y)%mo;
			k=max(k,d[b[i]]);
			k1=max(k1,c[a[l+1+i-x]]);
		}
	}else{
		fo(i,x,y){
			if (k1<i && k<=i-x+l){
				if (b[i]){
					if (!d[b[i]])t+=c1[num][f[l+1][i-x+l][x]]*dg(l+1,i-x+l,x,i-1)%mo*dg(i-x+l+1,r,i+1,y)%mo;
				}else t+=num*c1[num-1][f[l+1][i-x+l][x]]%mo*dg(l+1,i-x+l,x,i-1)%mo*dg(i-x+l+1,r,i+1,y)%mo;
			}
			k=max(k,d[b[i]]);
			k1=max(k1,c[a[l+1+i-x]]);
		}
	}
	return g[l][r][x]=t%mo;
}
int main(){
	//freopen("data.in","r",stdin);
	//freopen("data.out","w",stdout);
	scanf("%lld",&T);
	c1[0][0]=1;
	fo(i,1,100){
		c1[i][0]=1;
		fo(j,1,i) c1[i][j]=(c1[i-1][j-1]+c1[i-1][j])%mo;
	}
	while (T--){
		scanf("%lld",&n);
		memset(c,0,sizeof(c));
		memset(d,0,sizeof(d));
		fo(i,1,n)scanf("%lld",&a[i]),d[a[i]]=i;
		fo(i,1,n)scanf("%lld",&b[i]),c[b[i]]=i;
		fo(i,1,n)
			fo(j,1,n){
				memset(bz,0,sizeof(bz));num=k=0;
				while (i+k<=n && j+k<=n){
					if (!bz[a[i+k]] && a[i+k])bz[a[i+k]]=1,num++;
					if (!bz[b[j+k]] && b[j+k])bz[b[j+k]]=1,num++;
					f[i][i+k][j]=k+1-num;
					g[i][i+k][j]=-1;
					k++;
				}
			}
		c[0]=d[0]=0;
		ll ans;
		ans=dg(1,n,1,n);
		printf("%lld\n",ans);
	}
}

HDU - 6590 Code (模擬?

description
(題目套了個AI的背景hhh,不過維數只有2)
y=sign(x1w1+x2w2+b)y=sign(x1*w1+x2*w2+b)
sign(t)={1t>00t=01t<0 sign(t)=\begin{cases} 1 & t>0 \\ 0 & t=0 \\ -1 & t<0 \end{cases}
給定x1,x2,yx1,x2,y(PPS:題目給的y只會是1或者-1)
問是否存在w1,w2,bw1,w2,b滿足所有的“樣本”

solution
(最開始沒看到這個sign。。寫了一個高斯消元着實丟人
因爲題目給的y是1或者-1
我們就可以動點腦筋
假如有yi=1yj=1y_i=1 並且 y_j=-1我們就可以推導出
sign(x1iw1+x2iw2+b)>sign(x1jw1+x2jw2+b) sign(x1_i*w1+x2_i*w2+b)>sign(x1_j*w1+x2_j*w2+b)
x1iw1+x2iw2+b>x1jw1+x2jw2+bx1_i*w1+x2_i*w2+b>x1_j*w1+x2_j*w2+b
x1iw1+x2iw2>x1jw1+x2jw2x1_i*w1+x2_i*w2>x1_j*w1+x2_j*w2
x1iw1x1jw1>x2jw2x2iw2x1_i*w1-x1_j*w1>x2_j*w2-x2_i*w2
(x1ix1j)w1>(x2jx2i)w2(x1_i-x1_j)*w1>(x2_j-x2_i)*w2
這個式子應該永遠成立

然而兩個變量不好處理於是乎根據高中數學導數題的常用套路,左右兩邊同時除以w1(記得特殊考慮w1=0的情況!)

然後就是解不等式組 看看有沒有解嘍
(有人可能會覺得只有一個方向肯定有解啊?)
(一定要小心處理乘除負數導致不等號方向改變的情況!)

code

#include<bits/stdc++.h>
typedef long long LL; 
const int oo=0x3f3f3f3f; 
const int N=110; 
const int MOD=1e9+7;
using namespace std;
int read(){
	int f=1,s=0;char c=getchar();
	for(;c<'0'||c>'9';c=getchar())if(c=='-')f=-1;
	for(;c>='0'&&c<='9';c=getchar())s=s*10+c-'0';
	return f*s;
}

int x1[N],x2[N],y[N];
bool work(){
	int n=read();
	for(int i=0;i<n;i++){
		x1[i]=read();
		x2[i]=read();
		y[i]=read();
	}
	//w1>0
	double L=-1LL<<31,R=1LL<<31;
	for(int i=0;i<n;i++)
		for(int j=0;j<n;j++){
			if(i==j)continue;
			if(y[i]==1&&y[j]==-1){
				int fenmu=x2[j]-x2[i];
				int fenzi=x1[i]-x1[j];
				if(fenmu==0)continue;
				if(fenmu<0)L=max(L,1.0*fenzi/fenmu);
				else R=min(R,1.0*fenzi/fenmu);
			}
		}
//	printf("%.3lf %.3lf\n",L,R);
	if(L<=R)return true;
	
	
		
	//w1==0	
	L=-1LL<<31,R=1LL<<31;
	for(int i=0;i<n;i++)
		for(int j=0;j<n;j++){
			if(i==j)continue;
			if(y[i]==1&&y[j]==-1){
				int fenmu=x2[j]-x2[i];
				int fenzi=0;
				if(fenmu==0)continue;
				if(fenmu>0)L=max(L,1.0);
				else R=min(R,-1.0);
			}
		}
	//printf("%.3lf %.3lf\n",L,R);	
	if(L<=R)return true;
	
	//w1<0
	L=-1LL<<31,R=1LL<<31;
	for(int i=0;i<n;i++)
		for(int j=0;j<n;j++){
			if(i==j)continue;
			if(y[i]==1&&y[j]==-1){
				int fenmu=x2[j]-x2[i];
				int fenzi=x1[i]-x1[j];
				if(fenmu==0)continue;
				if(fenmu>0)L=max(L,1.0*fenzi/fenmu);
				else R=min(R,1.0*fenzi/fenmu);
			}
		}
//	printf("%.3lf %.3lf\n",L,R);
	if(L<=R)return true;
	
	return false;
	
} 


int main(){
	int T=read();
	for(int i=1;i<=T;i++){
		if(work()==true)printf("Successful!\n");
		else printf("Infinite loop!\n");
	}
	return 0;
} 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章