l o n g long long l o n g long long 的范围: [ − 2 63 , 2 63 − 1 ] [-2^{63},2^{63}-1] [−263,263−1]
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,264−1]
以后做题要注意细节
#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;
}
将问题抽象,有一个 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=1∏n(1+(j=1∑ma[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[i−1][j][k]+f[i−1][j−1][k]×a[i][p]+f[i−1][j][k−1]×(sum−a[i][p])
s u m = ∑ l = 1 m a [ i ] [ l ] sum=\sum_{l=1}^{m}{a[i][l]} sum=l=1∑ma[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=all−j>k∑f[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[i−1][j]+f[i−1][j−1]×a[i][p]+f[i−1][j+1]×(sum−a[i][p])
s u m = ∑ l = 1 m a [ i ] [ l ] sum=\sum_{l=1}^{m}{a[i][l]} sum=l=1∑ma[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=all−j>0∑f[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[j−1]>=g[j−1]
并且如果 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[j−1]+(sum[i]−sum[j−1]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[j−1]+(sum[i]−sum[j−1])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;
}
给定一棵 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;
}
#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;
}