A | B | C | D | E | F | G | H | I | J | K | L | M |
---|---|---|---|---|---|---|---|---|---|---|---|---|
○ | √ | √ | √ | ○ | ●○ |
( √:做出; ●:尝试未做出; ○:已补题 )
A Blank
题目地址: http://acm.hdu.edu.cn/showproblem.php?pid=6578
题意:n块连续的板,每块板可以涂色 c ∈ { 0 , 1 , 2 , 3 } c\in \{0,1,2,3\} c∈{0,1,2,3},且有 m 个限制,每个限制 ( l i , r i , x i ) (l_i,r_i,x_i) (li,ri,xi) 要求 [ l i , r i ] [l_i,r_i] [li,ri] 中的所有板恰好有 x i x_i xi 种颜色。问总共有多少种涂色方案。
思路:dp。设 f[i][j][k][q] 表示四种颜色从小到大最后出现的位置为 i、j、k、q 时的涂色方案,显然计算这个时,当前涂色涂到第 q 块板。往后推比较容易想:
f
[
i
]
[
j
]
[
k
]
[
q
+
1
]
+
=
f
[
i
]
[
j
]
[
k
]
[
q
]
f
[
j
]
[
k
]
[
q
]
[
q
+
1
]
+
=
f
[
i
]
[
j
]
[
k
]
[
q
]
f
[
i
]
[
k
]
[
q
]
[
q
+
1
]
+
=
f
[
i
]
[
j
]
[
k
]
[
q
]
f
[
i
]
[
j
]
[
q
]
[
q
+
1
]
+
=
f
[
i
]
[
j
]
[
k
]
[
q
]
f[i][j][k][q+1]+=f[i][j][k][q] \\f[j][k][q][q+1]+=f[i][j][k][q]\\f[i][k][q][q+1]+=f[i][j][k][q]\\f[i][j][q][q+1]+=f[i][j][k][q]\\
f[i][j][k][q+1]+=f[i][j][k][q]f[j][k][q][q+1]+=f[i][j][k][q]f[i][k][q][q+1]+=f[i][j][k][q]f[i][j][q][q+1]+=f[i][j][k][q]
最后一维只有相邻两两相关,可以滚动节省空间;然后处理第 q 块板时,找出
r
i
=
q
r_i=q
ri=q 的限制,看 最后出现的位置在限制范围内的数目是否等于限制个数 来决定取值是否不为 0 。
不得不说对于这种需要天马行空想象力来dp的题目,经验还是不够。其实这道题数据范围很小,而且是一个计数问题,dp应该是惯常思路,以后要注意。
代码:
#pragma GCC optimize(2)
#include <bits/stdc++.h>
#define mem(a,b) memset(a,b,sizeof(a))
#define REP(i,a,b) for(int i=(a);i<=(int)(b);i++)
#define REP_(i,a,b) for(int i=(a);i>=(b);i--)
using namespace std;
typedef long long LL;
int read()
{
int x=0,flag=1;
char c=getchar();
while((c>'9' || c<'0') && c!='-') c=getchar();
if(c=='-') flag=0,c=getchar();
while(c<='9' && c>='0') {x=(x<<3)+(x<<1)+c-'0';c=getchar();}
return flag?x:-x;
}
const int maxn=102,M=998244353;
int T,n,m,f[maxn][maxn][maxn][2];
int main()
{
//freopen("input.txt","r",stdin);
T=read();
while(T--)
{
n=read(),m=read();
REP(i,0,n) REP(j,i,n) REP(k,j,n) mem(f[i][j][k],0);
vector<pair<int,int>> L[n+2];
while(m--)
{
int l=read(),r=read(),x=read();
L[r].push_back(make_pair(l,x));
}
f[0][0][0][0]=1;
int cur=0;
REP(l,1,n)
{
cur^=1;
REP(i,0,l) REP(j,i,l) REP(k,j,l) f[i][j][k][cur]=0;
REP(i,0,l) REP(j,i,l) REP(k,j,l)
{
f[i][j][k][cur]=(f[i][j][k][cur]+f[i][j][k][cur^1])%M;
f[j][k][l-1][cur]=(f[j][k][l-1][cur]+f[i][j][k][cur^1])%M;
f[i][k][l-1][cur]=(f[i][k][l-1][cur]+f[i][j][k][cur^1])%M;
f[i][j][l-1][cur]=(f[i][j][l-1][cur]+f[i][j][k][cur^1])%M;
REP(v,0,L[l].size()-1)
{
int a=L[l][v].first,x=L[l][v].second;
#define can(i,j,k,q) ((i>=a)+(j>=a)+(k>=a)+(q>=a)==x)
if(!can(i,j,k,l)) f[i][j][k][cur]=0;
if(!can(j,k,l-1,l)) f[j][k][l-1][cur]=0;
if(!can(i,k,l-1,l)) f[i][k][l-1][cur]=0;
if(!can(i,j,l-1,l)) f[i][j][l-1][cur]=0;
}
}
}
int ans=0;
REP(i,0,n) REP(j,i,n) REP(k,j,n)
ans=(ans+f[i][j][k][cur])%M;
cout<<ans<<endl;
}
return 0;
}
B Operation
题目地址: http://acm.hdu.edu.cn/showproblem.php?pid=6579
题意:有一个整数数列,两种操作:(1) 给定l, r,从 [l, r] 中选若干个数使得其异或和最大,打印最大异或和;(2) 在数列尾部添加一个数。
思路:很明显的线性基的题目。一开始我的思路是用线段树维护区间线性基,不过这样由于线段树每个结点都是几十个int,会MLE,所以不行。考虑线性基+前缀和,每一个位置记录的线性基是前面所有数的线性基,当然我们要保证线性基的选取尽可能接近这个数,也就是说,我们要记录每个位的基的选取位置,然后插入的时候尽可能使这个位置靠右,这样当选取 [l, r] 中的线性基求最大异或和时,就可以等价于选取 r 的线性基前缀和,然后判断每个位的基的选取位置是否大于等于 l 来决定这个位是否有效。
代码:
#pragma GCC optimize(2)
#include <bits/stdc++.h>
#define mem(a,b) memset(a,b,sizeof(a))
#define REP(i,a,b) for(int i=(a);i<=(int)(b);i++)
#define REP_(i,a,b) for(int i=(a);i>=(b);i--)
using namespace std;
typedef long long LL;
int read()
{
int x=0,flag=1;
char c=getchar();
while((c>'9' || c<'0') && c!='-') c=getchar();
if(c=='-') flag=0,c=getchar();
while(c<='9' && c>='0') {x=(x<<3)+(x<<1)+c-'0';c=getchar();}
return flag?x:-x;
}
struct linear_basis
{
static const int N=29;
int b[N+1],t[N+1];
linear_basis() {mem(b,0),mem(t,0);}
void insert(int x,int id)
{
for(int i=N;i>=0;i--)
if(x&(1<<i))
{
if(!b[i]) {b[i]=x; t[i]=id; break;}
else if(id>t[i])
{
swap(b[i],x);
swap(t[i],id); // 这里用替换是因为可能这个被替换的基还能比之前的更优
}
x^=b[i];
}
}
int max_sum(int l)
{
int ans=0;
for(int i=N;i>=0;i--)
if(t[i]>=l && (ans^b[i])>ans) ans^=b[i];
return ans;
}
};
const int maxn=1e6+5;
linear_basis lb[maxn];
int main()
{
//freopen("input.txt","r",stdin);
int n,m,T=read();
while(T--)
{
n=read(),m=read();
REP(i,1,n)
{
lb[i]=lb[i-1];
int a=read();
lb[i].insert(a,i);
}
int ans=0;
while(m--)
{
int op=read();
if(op==1)
{
int x=read()^ans;
n++;
lb[n]=lb[n-1];
lb[n].insert(x,n);
}
else
{
int l=(read()^ans)%n+1;
int r=(read()^ans)%n+1;
if(l>r) swap(l,r);
ans=lb[r].max_sum(l);
printf("%d\n",ans);
}
}
//printf("%d\n",st.n);
}
return 0;
}
C
题目地址:
题意:
思路:
代码:
D Vacation
题目地址: http://acm.hdu.edu.cn/showproblem.php?pid=6581
题意:马路上有n辆车,每辆车定义了长度、速度和车头距终点的距离。不能超车,并且当车经过终点仍然保持原来的速度继续前进,问最后(最远)那辆车要经过多久可以到达终点。
思路:直接计算时间似乎比较复杂,不过由于答案具有单调性,故可以二分。对于某个固定时间t,可以算出第一辆车(最前面的车)到达的位置,然后可以算出第二辆车到达的位置(然后根据前面的来取值),…,这样就可以算出最后一辆车到达的位置来判断这个时间是否可行。
代码:
#pragma GCC optimize(2)
#include <bits/stdc++.h>
#define mem(a,b) memset(a,b,sizeof(a))
#define REP(i,a,b) for(int i=(a);i<=(int)(b);i++)
#define REP_(i,a,b) for(int i=(a);i>=(b);i--)
using namespace std;
typedef long long LL;
int read()
{
int x=0,flag=1;
char c=getchar();
while((c>'9' || c<'0') && c!='-') c=getchar();
if(c=='-') flag=0,c=getchar();
while(c<='9' && c>='0') {x=(x<<3)+(x<<1)+c-'0';c=getchar();}
return flag?x:-x;
}
const int maxn=1e5+5;
double l[maxn],s[maxn],v[maxn],temp[maxn];
int n;
bool can(double t)
{
temp[n]=s[n]-v[n]*t;
REP_(i,n-1,0)
{
double q=s[i]-v[i]*t;
if(q<temp[i+1]+l[i+1]) q=temp[i+1]+l[i+1];
temp[i]=q;
}
return temp[0]<=0;
}
int main()
{
//freopen("input.txt","r",stdin);
while(~scanf("%d",&n))
{
REP(i,0,n) l[i]=read();
REP(i,0,n) s[i]=read();
REP(i,0,n) v[i]=read();
double ll=0,rr=1e9+5,mid;
while(rr-ll>1e-9)
{
mid=(ll+rr)/2;
if(can(mid)) rr=mid;
else ll=mid;
}
printf("%.9f\n",rr);
}
return 0;
}
E Path
题目地址: http://acm.hdu.edu.cn/showproblem.php?pid=6582
题意:有一张有向图,我们可以删边,问至少需要删去总长度为多少的边时,可以使得从1-n的最短路的长度变长。
思路:我们首先找出哪些边属于“最短路边”(如果一条边属于“最短路边”,那么一定存在一条最短路包含这条边),找的方法很简单:首先求一次 1 结点的单源最短路 d1[],然后求一次反向建图的 n 结点的单源最短路 d2[](其实就是原图中各个点到 n 的最短路),那么一条边 <u, v, w> 属于“最短路边”,当且仅当 d1[u]+w+d2[v]=d1[n]。
把所有“最短路边”拿来建一张图,我们的目的就是删去尽可能权值和更小的边集,使得 1 和 n 不连通(那么原图中就不存在原来的最短路了),这就是一个最小割的问题。
代码:
#pragma GCC optimize(2)
#include <bits/stdc++.h>
#define mem(a,b) memset(a,b,sizeof(a))
#define REP(i,a,b) for(int i=(a);i<=(int)(b);i++)
#define REP_(i,a,b) for(int i=(a);i>=(b);i--)
using namespace std;
typedef long long LL;
int read()
{
int x=0,flag=1;
char c=getchar();
while((c>'9' || c<'0') && c!='-') c=getchar();
if(c=='-') flag=0,c=getchar();
while(c<='9' && c>='0') {x=(x<<3)+(x<<1)+c-'0';c=getchar();}
return flag?x:-x;
}
const int maxn=1e4+5;
const LL inf=1e16;
struct edge {int to; LL cap; int rev,is_rev;};
vector<edge> G[maxn];
LL dis[maxn];
int book[maxn];
struct edge_ {int v; LL w;};
struct _edge {int u,v; LL w;} e[maxn];
vector<edge_> GG[maxn];
int n,m,vis[maxn];
LL d[2][maxn];
void init()
{
REP(i,1,n) GG[i].clear(),G[i].clear();
}
void dijkstra(int s,LL *d)
{
fill(d,d+n+5,inf);
mem(vis,0);
d[s]=0;
typedef pair<LL,int> P;
priority_queue<P,vector<P>,greater<P> > Q;
Q.push(P(0,s));
while(!Q.empty())
{
P p=Q.top(); Q.pop();
LL dis=p.first;
int u=p.second;
if(vis[u]) continue;
d[u]=dis; vis[u]=1;
REP(i,0,GG[u].size()-1)
{
int v=GG[u][i].v;
LL w=GG[u][i].w;
if(vis[v] || dis+w>d[v]) continue;
Q.push(P(d[v]=dis+w,v));
}
}
}
void add_edge(int from,int to,LL cap)
{
G[from].push_back((edge){to,cap,(int)G[to].size(),0});
G[to].push_back((edge){from,0,(int)G[from].size()-1,1});
}
void BFS(int s)
{
mem(dis,-1); dis[s]=0;
queue<int> que; que.push(s);
while(!que.empty())
{
int v=que.front();que.pop();
REP(i,0,G[v].size()-1)
{
edge e=G[v][i];
if(dis[e.to]<0 && e.cap) dis[e.to]=dis[v]+1,que.push(e.to);
}
}
}
LL dfs(int s,int t,LL flow)
{
if(s==t) return flow;
for(int &i=book[s];i<(int)G[s].size();i++)
{
edge &e=G[s][i];
if(e.cap && dis[s]<dis[e.to])
{
LL flow2=dfs(e.to,t,min(flow,e.cap));
if(!flow2) continue;
e.cap-=flow2;
G[e.to][e.rev].cap+=flow2;
return flow2;
}
}
return 0;
}
LL max_flow(int s,int t)
{
LL flow=0,flow2;
while(1)
{
BFS(s);
if(dis[t]<0) return flow;
mem(book,0);
while((flow2=dfs(s,t,inf))>0) flow+=flow2;
}
}
int main()
{
//freopen("input.txt","r",stdin);
int T=read();
while(T--)
{
n=read(),m=read();
init();
REP(i,1,m)
{
int u=read(),v=read(); LL w=read();
GG[u].push_back((edge_){v,w});
e[i]=(_edge){u,v,w};
}
dijkstra(1,d[0]);
init();
REP(i,1,m) GG[e[i].v].push_back((edge_){e[i].u,e[i].w});
dijkstra(n,d[1]);
REP(i,1,m)
{
int u=e[i].u,v=e[i].v;
LL w=e[i].w;
if(d[0][u]+w+d[1][v]==d[0][n])
add_edge(u,v,w);
}
printf("%lld\n",max_flow(1,n));
}
return 0;
}
F Typewriter
题目地址: http://acm.hdu.edu.cn/showproblem.php?pid=6583
题意:有一个打字机,每次可以花费 p 去打印任意一个字符,或者花费 q 去打印之前打印过的一段字符串。给出一个字符串问打印该字符串的最少花费是多少。
思路:dp+SAM。dp的思路应该比较好想,设 f[i] 为打印到第 i 个字符的最少花费,那么必然有
f
[
i
]
=
m
i
n
{
f
[
i
−
1
]
+
p
,
f
[
j
]
+
q
}
f[i]=min\{f[i-1]+p, \ f[j]+q\}
f[i]=min{f[i−1]+p, f[j]+q}
其中,j 是最小的下标,使得子串 s[j+1,…,i] 是 s[1,…,j] 的子串。问题就在于如何寻找这个最小的 j。可以发现,如果 i 越大,那么必然 j 也会越大,因为如果 j 反而更小,那么之前更小的 i 就肯定可以用这个更小的 j(更短的子串)。所以就可以维护一个 j 以及子串 s[1,…,j] 的前缀信息(SAM),并且保证 s[j+1,…,i] 是可以在 SAM 中匹配。对于一个新的 s[i+1],如果可以匹配自然 j 不变,否则,循环判断处理将 s[++j] 放进SAM(此时的 s[j+1,…,i] 仍然是子串),并且及时将当前匹配位点视情况跳link(因为 j++了,所以当前的匹配字符串长度变小了,就有可能变成了当前匹配位点的后缀连接的子串集中的某一个)即可。
代码:
#pragma GCC optimize(2)
#include <bits/stdc++.h>
#define mem(a,b) memset(a,b,sizeof(a))
#define REP(i,a,b) for(int i=(a);i<=(int)(b);i++)
#define REP_(i,a,b) for(int i=(a);i>=(b);i--)
using namespace std;
typedef long long LL;
int read()
{
int x=0,flag=1;
char c=getchar();
while((c>'9' || c<'0') && c!='-') c=getchar();
if(c=='-') flag=0,c=getchar();
while(c<='9' && c>='0') {x=(x<<3)+(x<<1)+c-'0';c=getchar();}
return flag?x:-x;
}
// 1号是S结点
struct suffix_automaton
{
#define maxn ((int)(2e5+5))
int maxlen[maxn<<1],trans[maxn<<1][26],link[maxn<<1],tot=1,last=1;
void init()
{
REP(i,1,tot) maxlen[i]=link[i]=0,mem(trans[i],0);
tot=last=1;
}
void extend(int c)
{
int cur=++tot,p;
maxlen[cur]=maxlen[last]+1;
for(p=last;p && !trans[p][c];p=link[p]) trans[p][c]=cur;
if(!p) link[cur]=1;
else
{
int q=trans[p][c];
if(maxlen[q]==maxlen[p]+1) link[cur]=q;
else
{
int clone=++tot;
maxlen[clone]=maxlen[p]+1;
memcpy(trans[clone],trans[q],sizeof(trans[q]));
for(;p && trans[p][c]==q;p=link[p]) trans[p][c]=clone;
link[clone]=link[q];
link[q]=link[cur]=clone;
}
}
last=cur;
}
};
char s[maxn];
suffix_automaton sam;
LL f[maxn],p,q;
int main()
{
//freopen("input.txt","r",stdin);
while(~scanf("%s",s+1))
{
int n=strlen(s+1);
p=read(),q=read();
sam.init();
int j=0,now=1;
f[1]=p;
sam.extend(s[++j]-'a');
REP(i,2,n)
{
f[i]=f[i-1]+p;
while(now>1 && i-j-1<=sam.maxlen[sam.link[now]]) now=sam.link[now];
while(!sam.trans[now][s[i]-'a'])
{
sam.extend(s[++j]-'a');
while(now>1 && i-j-1<=sam.maxlen[sam.link[now]]) now=sam.link[now];
}
while(now>1 && i-j-1<=sam.maxlen[sam.link[now]]) now=sam.link[now];
now=sam.trans[now][s[i]-'a'];
f[i]=min(f[i],f[j]+q);
//cout<<i<<' '<<j<<endl;
}
printf("%lld\n",f[n]);
}
return 0;
}
G
题目地址:
题意:
思路:
代码:
H
题目地址:
题意:
思路:
代码:
I String
题目地址: http://acm.hdu.edu.cn/showproblem.php?pid=6586
题意:给出一个仅包含小写字母的字符串,要求选出长度为k的子序列(不要求连续),使得字典序最小,并且每个字母出现次数在 L[c] 和 R[c] 之间。
思路:其实就是一个贪心的思路,组队模拟赛的时候本来已经有思路了,但不知道为什么写着写着思路就混乱,写成了“贪心地按字母顺序放入”。正解应该是“从前到后对于每一位,贪心地选取满足条件的最小的字母”,这里满足条件主要是指如果当前位置填了这个字母,那么要保证后面有足够的字母能够满足限制,以及剩余位置足够满足限制等等,满足条件还是要细心想一想。
可以给每个字母建一个队列,并且记录上一个选择字母的坐标,然后贪心即可。
代码:
#pragma GCC optimize(2)
#include <bits/stdc++.h>
#define mem(a,b) memset(a,b,sizeof(a))
#define REP(i,a,b) for(int i=(a);i<=(int)(b);i++)
#define REP_(i,a,b) for(int i=(a);i>=(b);i--)
using namespace std;
typedef long long LL;
int read()
{
int x=0,flag=1;
char c=getchar();
while((c>'9' || c<'0') && c!='-') c=getchar();
if(c=='-') flag=0,c=getchar();
while(c<='9' && c>='0') {x=(x<<3)+(x<<1)+c-'0';c=getchar();}
return flag?x:-x;
}
const int maxn=1e5+5;
int k,n,L[maxn],R[maxn],num[200],f[127][maxn],ans[maxn];
char s[maxn];
queue<int> que[127];
bool can(int w,int tot)
{
if(tot+n-w<k) return 0;
REP(i,'a','z') if(num[i]+f[i][w+1]<L[i]) return 0;
int sum=0;
REP(i,'a','z') if(L[i]>num[i]) sum+=L[i]-num[i];
if(k-tot<sum) return 0;
return 1;
}
int main()
{
//freopen("input.txt","r",stdin);
while(~scanf("%s",s+1))
{
n=strlen(s+1); k=read(); mem(num,0);
REP(i,'a','z') L[i]=read(),R[i]=read(),f[i][n+1]=0;
REP(i,'a','z') while(!que[i].empty()) que[i].pop();
REP_(i,n,1) REP(j,'a','z')
f[j][i]=(j==s[i])?f[j][i+1]+1:f[j][i+1];
REP(i,1,n) que[s[i]].push(i);
int r=0,tot=0;
REP(i,1,k) REP(c,'a','z')
{
while(!que[c].empty() && que[c].front()<=r) que[c].pop();
if(que[c].empty() || num[c]>=R[c]) continue;
num[c]++;
if(can(que[c].front(),tot+1)) {r=que[c].front(); ans[tot++]=c; break;}
else num[c]--;
}
if(tot<k) puts("-1");
else
{
REP(i,0,k-1) putchar(ans[i]);
puts("");
}
}
return 0;
}
J
题目地址:
题意:
思路:
代码:
K
题目地址:
题意:
思路:
代码:
L
题目地址:
题意:
思路:
代码:
M
题目地址:
题意:
思路:
代码: