2019.12.3

格雷码

  • 通过样例前三位的格雷码可以发现,靠在这一位格雷码个数前一半的构成答案为 0 0 0,如果靠在后半段则答案这一位为 1 1 1。然后模拟舍去一半,看在下一位中是靠前还是靠后。
  • 要用到 2 64 − 1 2^{64}-1 2641,要用 u n s i g n e d unsigned unsigned l o n g long long l o n g long long

l o n g long long l o n g long long 的范围: [ − 2 63 , 2 63 − 1 ] [-2^{63},2^{63}-1] [263,2631]

u n s i g n e d unsigned unsigned l o n g long long l o n g long long: [ 0 , 2 64 − 1 ] [0,2^{64}-1] [0,2641]

​ 以后做题要注意细节

#include 
using namespace std;
#define LL unsigned long long 
inline LL read_() {
	LL x_=0,f_=1;char c_=getchar();
	while(c_<'0'||c_>'9') {if(c_=='-') f_=-1;c_=getchar();}
	while(c_>='0'&&c_<='9') {x_=(x_<<1)+(x_<<3)+c_-'0';c_=getchar();}
	return x_*f_;
}

inline LL quick_pow(LL a,LL b) {
	if( ! b ) return 1;
	LL ans = 1;
	while( b ) {
		if( b & 1 ) ans = ans * a;
		a = a * a;
		b >>= 1;
	}
	return ans;
}
LL n,k;
int ans[100];

int main() {
	n = read_();k = read_();
	int cnt = 0;
	for(int i = n;i >= 1;--i) {
		LL a = quick_pow(2,i-1) - 1;
		if( k > a ) {
			ans[++cnt] = 1;
			k = a - ( k - a ) + 1; 
		}
		else ans[++cnt] = 0; 
	}
	for(int i = 1;i <= cnt;++i) printf("%d",ans[i]);
	return 0;
}

括号树

  • 两个问题:如何快速枚举一个串的子串,如何统计一个串的贡献。

    先解决第二个问题:

    按照表达式括号匹配的方法, t o p top top记录当前已有多少个 “ ( ” “(” (,如果当前位为 " ) " ")" ")",且 t o p > 0 top>0 top>0,那么就能贡献一个单独的合法子串。

    对于数据 ( ) ( ) ()() ()()显然答案是 3 3 3,按这种方法却是 2 2 2

    由题可知,两个合法的括号串放在一起也是一个合法的串。

    f [ i ] f[i] f[i]表示到第 i i i位的合法子串有多少个。再用 l a [ i ] la[i] la[i]表示到第 i i i位最靠右的 “ ( ” “(” (的位置。

​ 如果这一位为 " ( " "(" "(",则更新 l a [ i ] = i la[i]=i la[i]=i

​ 如果这一位为 " ( " "(" "(",则计算贡献,设 f a [ i ] fa[i] fa[i]表示第 i i i为的父亲节点

​ 如果 l a [ f a [ i ] ] la[fa[i]] la[fa[i]]不为 0 0 0:
f [ i ] = 1 + f [ f a [ l a [ f a [ i ] ] ] ] f[i]=1+f[ fa[la[fa[i]]] ] f[i]=1+f[fa[la[fa[i]]]]
​ 再更新 l a [ i ] la[i] la[i]:
l a [ i ] = l a [ f a [ l a [ f a [ i ] ] ] ] la[i]=la[fa[la[fa[i]]]] la[i]=la[fa[la[fa[i]]]]
​ 而对于第 i i i号点所有子串的答案,求个前缀和就好了

#include 
using namespace std;
inline int read_() {
	int x_=0,f_=1;char c_=getchar();
	while(c_<'0'||c_>'9') {if(c_=='-') f_=-1;c_=getchar();}
	while(c_>='0'&&c_<='9') {x_=(x_<<1)+(x_<<3)+c_-'0';c_=getchar();}
	return x_*f_;
}
#define maxn 500010
int tot = 0,n,head[maxn],la[maxn],fa[maxn];
long long f[maxn];
char s[maxn];
struct edge {
	int v,nxt;
}e[maxn<<1];
inline void add_(int u,int v) {
	e[++tot].v = v;
	e[tot].nxt = head[u];
	head[u] = tot;
}

void dfs_(int u) {
	if( s[u] == '(' ) la[u] = u;
	else {
		if( la[fa[u]] ) {
			f[u] = 1 + f[ fa[ la[fa[u]] ] ];
			la[u] = la[ fa[ la[fa[u]] ] ];
		}
	}
	for(int i = head[u];~i;i = e[i].nxt) {
		int v = e[i].v;
		dfs_(v);
	}
}

void get_(int u) {
	for(int i = head[u];~i;i = e[i].nxt) {
		int v = e[i].v;
		f[v] += f[u];
		get_(v);		
	}
}

int main() {
    //freopen("brackets.in","r",stdin);	
	//freopen("brackets.out","w",stdout);
    memset(head,-1,sizeof(head));
	n = read_();
    scanf("%s",s+1);
    for(int i = 2;i <= n;++i) {
    	fa[i] = read_();
    	add_(fa[i],i);
	}
	dfs_(1);
	get_(1); 
	long long ans = 0;
	for(long long i = 1;i <= n;++i) {
		ans ^= ( i * f[i] );
	}
	printf("%lld",ans);
	return 0;
}

Emiya 家今天的饭

  • 将问题抽象,有一个 n × m n\times{m} n×m的矩阵,第 ( i , j ) (i,j) (i,j)位置有 a [ i ] [ j ] a[i][j] a[i][j]中不同的选法,求每一行至多选一个,且总的至少选一个,并且每一列所选的位置不能超过 ⌊ s u m 2 ⌋ \left \lfloor {\frac{sum}{2}} \right \rfloor{} 2sum

  • 假设没有条件三,那么总的方案数就为:
    a l l = ∏ i = 1 n ( 1 + ( ∑ j = 1 m a [ i ] [ j ] ) ) − 1 all=\prod_{i=1}^{n}{(1+(\sum_{j=1}^{m}{a[i][j]}))-1} all=i=1n(1+(j=1ma[i][j]))1

+ 1 +1 +1为这一行一个都不选, − 1 -1 1是排除一个菜都没有的方案

​ 有了条件三又怎么做 ? ? ?

​ 考虑容斥,算出一列超出 ⌊ s u m 2 ⌋ \left \lfloor {\frac{sum}{2}} \right \rfloor{} 2sum的方案数,再用 a l l all all去减。

​ 只可能有一列超出了 ⌊ s u m 2 ⌋ \left \lfloor {\frac{sum}{2}} \right \rfloor{} 2sum

​ 枚举超出限制的是哪一列 p p p,然后计算方案数。

​ 定义 f [ i ] [ j ] [ k ] f[i][j][k] f[i][j][k]表示前 i i i行,枚举的第 p p p列选了 j j j个,其他列共选了 k k k个的方案数

​ 边界: f [ 0 ] [ 0 ] [ 0 ] = 1 f[0][0][0]=1 f[0][0][0]=1
f [ i ] [ j ] [ k ] = f [ i − 1 ] [ j ] [ k ] + f [ i − 1 ] [ j − 1 ] [ k ] × a [ i ] [ p ] + f [ i − 1 ] [ j ] [ k − 1 ] × ( s u m − a [ i ] [ p ] ) f[i][j][k]=f[i-1][j][k]+f[i-1][j-1][k]\times{a[i][p]+f[i-1][j][k-1]\times{(sum-a[i][p])}} f[i][j][k]=f[i1][j][k]+f[i1][j1][k]×a[i][p]+f[i1][j][k1]×(suma[i][p])

s u m = ∑ l = 1 m a [ i ] [ l ] sum=\sum_{l=1}^{m}{a[i][l]} sum=l=1ma[i][l]

​ 对于每一次枚举的 p p p
a n s = a l l − ∑ j > k f [ n ] [ j ] [ k ] ans=all-\sum_{j>k}{f[n][j][k]} ans=allj>kf[n][j][k]

时间复杂度 O ( m × n 3 ) O(m\times{n^{3}}) O(m×n3),可以拿到 84 84 84

#include 
using namespace std;
inline int read_() {
	int x_=0,f_=1;char c_=getchar();
	while(c_<'0'||c_>'9') {if(c_=='-') f_=-1;c_=getchar();}
	while(c_>='0'&&c_<='9') {x_=(x_<<1)+(x_<<3)+c_-'0';c_=getchar();}
	return x_*f_;
}

#define maxn 110
#define maxm 2010
#define LL long long 
#define mod 998244353
int n,m,a[maxn][maxm];
LL f[maxn][maxn][maxn],sum[maxn];

inline void work_() {
	for(int i = 1;i <= n;++i) {
		for(int j = 1;j <= m;++j) {
			sum[i] = ( sum[i] + a[i][j] ) % mod;
		}
	} 
	LL ans = 1;
	for(int i = 1;i <= n;++i) ans = ans * ( sum[i] + 1 ) % mod;    
	for(int p = 1;p <= m;++p) {
		f[0][0][0] = 1;
		for(int i = 1;i <= n;++i) {
			for(int j = 0;j <= i;++j) {
				for(int k = 0;k <= i;++k) {
					if( j + k > i ) break;
					f[i][j][k] = f[i-1][j][k] % mod;
					if( j >= 1 ) 
					f[i][j][k] = ( f[i][j][k] + f[i-1][j-1][k] * a[i][p] % mod );
					if( k >= 1 ) 
					f[i][j][k] = ( f[i][j][k] + f[i-1][j][k-1] * ( sum[i] - a[i][p] ) % mod ) % mod;
					if( i == n && j > k ) {
						ans = ( ans - f[i][j][k] ) % mod;
					}
				}
			}
		}
	}
	--ans;// 减去一个菜都没有的方案数 
	printf("%lld",( ans % mod + mod ) % mod);
}

int main() {
  //  freopen("meal.in","r",stdin);
    //freopen("meal.out","w",stdout);
    n = read_();m = read_();
    for(int i = 1;i <= n;++i) {
    	for(int j = 1;j <= m;++j) {
    		a[i][j] = read_();
		}
	}
	work_();
	return 0;
}
  • 如何优化:

    两个方向:加快转移,减少状态。只能选择减少状态

    由于我们只需要用到 j > k j>k j>k时的 f f f

​ 所以我们只关心$ j 比 比 k$多选了几个

​ 还是枚举哪一列 p p p会超出限制,

​ 定义 f [ i ] [ j ] f[i][j] f[i][j]表示前 i i i行,多选了 j j j个的方案数

​ 边界: f [ 0 ] [ 0 ] = 1 f[0][0]=1 f[0][0]=1
f [ i ] [ j ] = f [ i − 1 ] [ j ] + f [ i − 1 ] [ j − 1 ] × a [ i ] [ p ] + f [ i − 1 ] [ j + 1 ] × ( s u m − a [ i ] [ p ] ) f[i][j]=f[i-1][j]+f[i-1][j-1]\times{a[i][p]+f[i-1][j+1]\times{(sum-a[i][p])}} f[i][j]=f[i1][j]+f[i1][j1]×a[i][p]+f[i1][j+1]×(suma[i][p])

s u m = ∑ l = 1 m a [ i ] [ l ] sum=\sum_{l=1}^{m}{a[i][l]} sum=l=1ma[i][l]

​ 对于每一次枚举的 p p p
a n s = a l l − ∑ j > 0 f [ n ] [ j ] ans=all-\sum_{j>0}{f[n][j]} ans=allj>0f[n][j]
时间复杂度 m × n 2 m\times{n^{2}} m×n2

新的问题:下标会成负数,整体加一个 N N N,使下标偏移

#include 
using namespace std;
inline int read_() {
	int x_=0,f_=1;char c_=getchar();
	while(c_<'0'||c_>'9') {if(c_=='-') f_=-1;c_=getchar();}
	while(c_>='0'&&c_<='9') {x_=(x_<<1)+(x_<<3)+c_-'0';c_=getchar();}
	return x_*f_;
}

#define maxn 110
#define maxm 2010
#define LL long long 
#define mod 998244353
int n,m,a[maxn][maxm],N = 110;
LL f[maxn][maxn<<2],sum[maxn];

inline void work_() {
	for(int i = 1;i <= n;++i) {
		for(int j = 1;j <= m;++j) {
			sum[i] = ( sum[i] + a[i][j] ) % mod;
		}
	} 
	LL ans = 1;
	for(int i = 1;i <= n;++i) ans = ans * ( sum[i] + 1 ) % mod;    
	for(int p = 1;p <= m;++p) {
		f[0][N] = 1;
		for(int i = 1;i <= n;++i) {
			for(int j = - n + N;j <= n + N;++j) {
				f[i][j] = ( f[i-1][j] % mod + f[i-1][j-1] * a[i][p] % mod + f[i-1][j+1] * ( sum[i] - a[i][p] ) % mod ) % mod;  		
				if( i == n && j > N ) {
					ans = ( ans - f[i][j] ) % mod; 
				} 
			}
		}
	} 
	--ans;//减去没有一道菜的方案 
	printf("%lld",( ans % mod + mod ) % mod );
}

int main() {
    //freopen("meal.in","r",stdin);
    //freopen("meal.out","w",stdout);
    n = read_();m = read_();
    for(int i = 1;i <= n;++i) {
    	for(int j = 1;j <= m;++j) {
    		a[i][j] = read_();
		} 
	}
	work_();
	return 0;
}

划分

  • 很容易想到一个 n 2 n^{2} n2 D P DP DP

    定义 f [ i ] f[i] f[i]表示以 i i i结尾的最小的时间和, g [ i ] g[i] g[i]表示以 i i i结尾时选择的最小的一个程序段

s u m [ i ] sum[i] sum[i]为前缀和

​ 如果 s u m [ i ] − s u m [ j − 1 ] > = g [ j − 1 ] sum[i]-sum[j-1]>=g[j-1] sum[i]sum[j1]>=g[j1]

​ 并且如果 f [ j − 1 ] + ( s u m [ i ] − s u m [ j − 1 ] 2 < = f [ i ] ) f[j-1]+(sum[i]-sum[j-1]^{2}<=f[i]) f[j1]+(sum[i]sum[j1]2<=f[i])
f [ i ] = f [ j − 1 ] + ( s u m [ i ] − s u m [ j − 1 ] ) 2 f[i]=f[j-1]+(sum[i]-sum[j-1])^{2} f[i]=f[j1]+(sum[i]sum[j1])2

g [ i ] = min ⁡ ( g [ i ] , p d c ) g[i]=\min(g[i],pdc) g[i]=min(g[i],pdc)

​ 可以得到 64 64 64

#include 
using namespace std;
inline int read_() {
	int x_=0,f_=1;char c_=getchar();
	while(c_<'0'||c_>'9') {if(c_=='-') f_=-1;c_=getchar();}
	while(c_>='0'&&c_<='9') {x_=(x_<<1)+(x_<<3)+c_-'0';c_=getchar();}
	return x_*f_;
}

#define maxn 5010
#define LL long long
#define INF 4000000000000000000 
int n,a[maxn];
LL sum[maxn],f[maxn],g[maxn];

inline void work_1_() {
	memset(f,0x7f,sizeof(f));
	memset(g,0x7f,sizeof(g));
	f[0] = g[0] = 0;
	sum[0] = 0;
	for(int i = 1;i <= n;++i) sum[i] = sum[i-1] + a[i];
    for(int i = 1;i <= n;++i) {
    	for(int j = 1;j <= i;++j) {
    		LL pdc = sum[i] - sum[j-1];
			if( pdc >= g[j-1] ) {
				LL ans = f[j-1] + pdc * pdc;
    			if( ans <= f[i] ) {
    				f[i] = ans;
    				g[i] = min( g[i],pdc );
    			}
    		}
    	}
    }
    printf("%lld",f[n]);
} 

int main() {
//	freopen("partition.in","r",stdin);
//	freopen("partition.out","w",stdout);
	n = read_();a[1] = read_();
	for(int i = 1;i <= n;++i) a[i] = read_();
	work_1_();
	return 0;
}

[Vani有约会]雨天的尾巴

  • 给定一棵 n n n个节点的树,每次给一条路径 ( u , v ) (u,v) (u,v)发放一种类型为 z z z的物品。最后问每个节点最多的物品是哪种类型 ? ? ?

  • 很容易想到一种暴力做法:离散化物品的类型。

    f [ u ] [ i ] f[u][i] f[u][i]表示 u u u号节点上类型为 i i i的物品有多少个。

    然后每次遍历修改数组,最后遍历询问答案, O ( n × m ) O(n\times{m}) O(n×m)

​ 有两个优化的方向:不遍历,优化询问答案

​ 可以使用树上点差分,对于修改路径 ( u , v , z ) (u,v,z) (u,v,z)

f [ u ] [ z ] + 1 , f [ v ] [ z ] + 1 , f [ l c a ] [ z ] − 1 , f [ f a [ l c a ] ] [ z ] − 1 f[u][z]+1,f[v][z]+1,f[lca][z]-1,f[fa[lca]][z]-1 f[u][z]+1,f[v][z]+1,f[lca][z]1,f[fa[lca]][z]1

​ 现在的问题就是最后在求前缀和的时候快速合并数组。

​ 使用动态开点线段树合并,修改在每个点的树上修改,最后在求前缀和的过程中将儿子的线段树合并到父亲上去。

#include 
using namespace std;
inline int read_() {
	int x_=0,f_=1;char c_=getchar();
	while(c_<'0'||c_>'9') {if(c_=='-') f_=-1;c_=getchar();}
	while(c_>='0'&&c_<='9') {x_=(x_<<1)+(x_<<3)+c_-'0';c_=getchar();}
	return x_*f_;
}

#define maxn 100010
int ans[maxn],len,dep[maxn],lg[maxn],f[maxn][30],val[maxn],n,head[maxn],m,tot = 0,num = 0,root[maxn];
struct edge {
	int v,nxt;
}e[maxn<<1];
struct QUERY_ {
	int x,y,z;
}Q[maxn];
struct SEG_TREE_ {
	int dat,pos,lc,rc;
}tr[maxn * 80];
inline void add_(int u,int v) {
	e[++tot].v = v;
	e[tot].nxt = head[u];
	head[u] = tot;
}

void dfs_(int u,int fa) {
	dep[u] = dep[fa] + 1;
	f[u][0] = fa;
	for(int i = 1;i <= lg[dep[u]];++i) {
		f[u][i] = f[f[u][i-1]][i-1];
	}
	for(int i = head[u];~i;i = e[i].nxt) {
		int v = e[i].v;
		if( v == fa ) continue;
		dfs_(v,u);
	}
}
inline int LCA_(int u,int v) {
	if( dep[u] < dep[v] ) swap(u,v);
	while( dep[u] > dep[v] ) {
		u = f[u][lg[dep[u]-dep[v]]];
    }
	if( u == v ) return v;
	for(int i = lg[dep[u]];i >= 0;--i) {
		if( f[u][i] != f[v][i] ) {
			u = f[u][i];
			v = f[v][i];
		}
	} 
	return f[u][0]; 
}

void update_(int l,int r,int nod,int k,int w) {
	if( l == r ) {
		tr[nod].dat += w;
		tr[nod].pos = tr[nod].dat ? l : 0; 
		return;
	}
	int mid = ( l + r ) >> 1;
	if( k <= mid ) {
		if( !tr[nod].lc ) tr[nod].lc = ++num;
		update_(l,mid,tr[nod].lc,k,w);
	}
	else {
		if( !tr[nod].rc ) tr[nod].rc = ++num;
		update_(mid+1,r,tr[nod].rc,k,w);
	}
	tr[nod].dat = max( tr[tr[nod].lc].dat,tr[tr[nod].rc].dat );
	tr[nod].pos = tr[tr[nod].lc].dat >= tr[tr[nod].rc].dat ? tr[tr[nod].lc].pos : tr[tr[nod].rc].pos;
}

int merge_(int u,int v,int l,int r) {
	if( !u ) return v;
	if( !v ) return u;
	if( l == r ) {
		tr[u].dat += tr[v].dat;
		tr[u].pos = tr[u].dat ? l : 0;
		return u;
	} 
	int mid = ( l + r ) >> 1;
	tr[u].lc = merge_(tr[u].lc,tr[v].lc,l,mid);
	tr[u].rc = merge_(tr[u].rc,tr[v].rc,mid+1,r);
	tr[u].dat = max( tr[tr[u].lc].dat,tr[tr[u].rc].dat );
	tr[u].pos = tr[tr[u].lc].dat >= tr[tr[u].rc].dat ? tr[tr[u].lc].pos : tr[tr[u].rc].pos; 
	return u;
}
void get_(int u,int fa) {
	for(int i = head[u];~i;i = e[i].nxt) {
		int v = e[i].v;
		if( v == fa ) continue;
		get_(v,u);
		root[u] = merge_(root[u],root[v],1,len);
	}
	ans[u] = tr[root[u]].pos;
}

int main() {
   // freopen("a.txt","r",stdin);
 //   freopen("ac.txt","w",stdout);
    memset(head,-1,sizeof(head));
    n = read_(),m = read_();
	for(int x,y,i = 1;i < n;++i) {
    	x = read_(),y = read_();
    	add_(x,y),add_(y,x);
	}
	for(int i = 2;i <= n;++i) lg[i] = lg[i>>1] + 1;
	dfs_(1,0);
	for(int i = 1;i <= n;++i) root[i] = ++num;
	for(int i = 1;i <= m;++i) {
		Q[i].x = read_();
		Q[i].y = read_();
		Q[i].z = read_();
		val[i] = Q[i].z;
	}
	sort(val+1,val+1+m);
	len = unique(val+1,val+1+m) - val - 1;
	for(int Z,lca,i = 1;i <= m;++i) {
// bug :Z = lower_bound(val+1,val+1+m,Q[i].z) - val	
		Z = lower_bound(val+1,val+1+len,Q[i].z) - val; 
		lca = LCA_(Q[i].x,Q[i].y);
		update_(1,len,root[Q[i].x],Z,1);
		update_(1,len,root[Q[i].y],Z,1);
		update_(1,len,root[lca],Z,-1);
		if( f[lca][0] ) update_(1,len,root[f[lca][0]],Z,-1);
	}
	get_(1,0);
	for(int i = 1;i <= n;++i) printf("%d\n",val[ans[i]]);
	return 0;
}

Dinic模板

#include 
using namespace std;
inline int read_() {
	int x_=0,f_=1;char c_=getchar();
	while(c_<'0'||c_>'9') {if(c_=='-') f_=-1;c_=getchar();}
	while(c_>='0'&&c_<='9') {x_=(x_<<1)+(x_<<3)+c_-'0';c_=getchar();}
	return x_*f_;
}
#define maxn 10010
#define maxm 100010
#define INF 1000000007
int n,tot,m,s,t,head[maxn],d[maxn],cur[maxn];
struct edge {
	int v,w,nxt;
}e[maxm<<1];
inline void add_(int u,int v,int w) {
	e[++tot].v = v;
	e[tot].w = w;
	e[tot].nxt = head[u];
	head[u] = tot;
}

inline bool bfs_() {
	memset(d,0,sizeof(d));
	d[s] = 1;
	queue<int>q;
	q.push(s);
	while( !q.empty() ) {
		int u = q.front();q.pop();
		for(int i = head[u];~i;i = e[i].nxt) {
			int v = e[i].v;
			if(  e[i].w && !d[v] ) {
				d[v] = d[u] + 1;
				q.push(v);
				if( v == t ) return true;
			}
		}
	} 
	return false;
}

int dinic_(int u,int flow) {
	if( u == t || !flow ) return flow;
	int rest = flow;
	for(int &i = cur[u];~i;i = e[i].nxt) {
		int v = e[i].v;
		if( e[i].w && d[v] == d[u] + 1 ) {
			int k = dinic_(v,min(rest,e[i].w));
			e[i].w -= k;
			e[i^1].w += k;
			rest -= k;
			if( !rest ) break;
		}
	}	
	return flow - rest;
}

int main() {
   // freopen("a.txt","r",stdin);
    memset(head,-1,sizeof(head));
	tot = 1;
	n = read_(),m = read_();
    s = read_(),t = read_();
    for(int x,y,z,i = 1;i <= m;++i) {
    	x = read_(),y = read_(),z = read_();
    	add_(x,y,z),add_(y,x,0);
    }
    int max_flow = 0;
    while( bfs_() ) {
    	memcpy(cur,head,sizeof(cur));
    	max_flow += dinic_(s,INF);
	}
	printf("%d",max_flow);
	return 0;
}

你可能感兴趣的:(每日总结)