2019 Multi-University Training Contest 1
目录(当前AC>500的题目,以后慢慢更新)
HDU - 6579 Operation (线性基+前缀预处理)
HDU - 6578 Blank (DP+滚动数组)
题意:
长度为n的数组要求分别填入{0,1,2,3}四个数中的任意一个,有m个限制条件:区间 [l,r] 中出现的数字种数恰好为x,求方案数
分析:
好奇葩的DP题,看了别人的题解后开明了许多,这种状态转移真的是学到了。
参考博客:https://www.cnblogs.com/zhuyou/p/11273721.html
首先, 我们可以用 来表示方案数, 不是特指对应某个数字,而是四种不同的数字从小到大最后出现的位置
那么我们就能推出方程:
我们发现后一位都只与 有关,的空间太大,所以我们要做一个优化,把最后一维改成大小为2的滚动数组
转移dp的时候考虑一下限制条件就OK啦.
#include<bits/stdc++.h>
#define mm(a,b) memset(a,b,sizeof(a))
#define ACCELERATE (ios::sync_with_stdio(false),cin.tie(0))
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
#define PI acos(-1.0)
#define E exp(1.0)
//#define io
using namespace std;
const int inf=0x3f3f3f3f;
const ll mod=998244353;
const int maxn=105;
ll dp[maxn][maxn][maxn][2];
vector<pair<int,int> > vec[maxn];
bool judge(int a,int b,int c,int d){
for(int i=0;i<vec[d].size();i++){
int l=vec[d][i].first;
int x=vec[d][i].second;
if(x==1&&l<=c) return false;
if(x==2&&(l<=b||l>c)) return false;
if(x==3&&(l<=a||l>b)) return false;
if(x==4&&(l>a)) return false;
}
return true;
}
int main(){
#ifdef io
freopen("in.txt","r",stdin);
freopen("out.txt","w",stdout);
#endif
int T;
scanf("%d",&T);
while(T--){
int n,m;
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++){
vec[i].clear();
}
while(m--){
int l,r,x;
scanf("%d%d%d",&l,&r,&x);
vec[r].push_back(make_pair(l,x));
}
dp[0][0][0][0]=1;
int now=1;
ll ans=0;
for(int w=1;w<=n;w++){
for(int k=0;k<=w;k++){
for(int j=0;j<=k;j++){
for(int i=0;i<=j;i++){
dp[i][j][k][now]=0;
}
}
}
for(int k=0;k<w||(!w&&k<=w);k++){
for(int j=0;j<k||(!k&&j<=k);j++){
for(int i=0;i<j||(!j&&i<=j);i++){
if(!judge(i,j,k,w-1)){
dp[i][j][k][now^1]=0;
continue;
}
dp[i][j][k][now]=(dp[i][j][k][now]+dp[i][j][k][now^1])%mod;
dp[i][j][w-1][now]=(dp[i][j][w-1][now]+dp[i][j][k][now^1])%mod;
dp[i][k][w-1][now]=(dp[i][k][w-1][now]+dp[i][j][k][now^1])%mod;
dp[j][k][w-1][now]=(dp[j][k][w-1][now]+dp[i][j][k][now^1])%mod;
}
}
}
now^=1;
}
now^=1;
for(int k=0;k<n;k++){
for(int j=0;j<k||(!k&&j<=k);j++){
for(int i=0;i<j||(!j&&i<=j);i++){
if(judge(i,j,k,n))
ans=(ans+dp[i][j][k][now])%mod;
}
}
}
printf("%lld\n",ans);
}
return 0;
}
HDU - 6579 Operation (线性基+前缀预处理)
题意:
计算从给定区间中选取一些数使得异或和最大。
分析:
cf上有一道题跟这个很像,但是那道题可以离线,而这道题必须在线。
但是大致思路差不多,考虑维护每个点的前缀线性基,线性基里将靠右的数字尽可能放高位,就是存一个额外存一个位置 p,表示这个位上的数的位置,从高位到低位扫,如果当前位置大于这个位上的位置那么交换,然后就得到了一个靠右的数字尽可能在高位的线性基然后对于询问 [l,r] 在 r 的前缀线性基里找,只在位置大于等于 l 的位更新答案
#include<bits/stdc++.h>
#define mm(a,b) memset(a,b,sizeof(a))
#define ACCELERATE (ios::sync_with_stdio(false),cin.tie(0))
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
#define PI acos(-1.0)
#define E exp(1.0)
//#define io
using namespace std;
const int inf=0x3f3f3f3f;
const int maxn=1e6+10;
struct Liner_Basis{
int d[32],nd[maxn][32];
int pos[32],npos[maxn][32];
void init(){
mm(d,0);
}
void insert(int x,int r){
int R=r;
for(int i=31;i>=0;i--){
if(x&(1<<i)){
if(!d[i]){
d[i]=x;
pos[i]=r;
break;
}else if(pos[i]<r){
swap(pos[i],r);
swap(d[i],x);
}
x^=d[i];
}
}
for(int i=31;i>=0;i--){
nd[R][i]=d[i];
npos[R][i]=pos[i];
}
}
int get_max(int l,int r){
int res=0;
for(int i=31;i>=0;i--){
if(npos[r][i]>=l){
res=max(res,res^nd[r][i]);
}
}
return res;
}
}lb;
int main(){
#ifdef io
freopen("in.txt","r",stdin);
freopen("out.txt","w",stdout);
#endif
int T;
scanf("%d",&T);
while(T--){
int n,m;
scanf("%d%d",&n,&m);
lb.init();
for(int i=1;i<=n;i++){
int a;
scanf("%d",&a);
lb.insert(a,i);
}
int last=0;
while(m--){
int op;
scanf("%d",&op);
if(op==0){
int l,r;
scanf("%d%d",&l,&r);
l=(l^last)%n+1;
r=(r^last)%n+1;
if(l>r) swap(l,r);
last=lb.get_max(l,r);
printf("%d\n",last);
}else{
int x;
scanf("%d",&x);
x^=last;
lb.insert(x,++n);
}
}
}
return 0;
}
HDU 6581 Vacation (模拟+二分)
模拟每辆车往前跑的过程,对时间进行二分,然后判断是否可行。
#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+10;
const double eps=1e-10;
double l[maxn],s[maxn],v[maxn],z[maxn];
int n;
bool judge(double t){
if(v[n]*t<s[n]) return false;
double up,len;
for(int i=1;i<=n;i++){
len=v[i]*t+z[i];
if(i==1){
up=len;
up-=l[i];
if(up<s[n]) return false;
}else if(i<n){
if(len<up) up=len;
up-=l[i];
if(up<s[n]) return false;
}else{
if(len<up) up=len;
if(up<s[n]) return false;
}
}
return true;
}
int main(){
while(scanf("%d",&n)!=EOF){
n++;
for(int i=n;i>=1;i--){
scanf("%lf",&l[i]);
}
double lim;
for(int i=n;i>=1;i--){
scanf("%lf",&s[i]);
if(i==n) lim=s[i],z[i]=0;
else z[i]=lim-s[i];
}
for(int i=n;i>=1;i--){
scanf("%lf",&v[i]);
}
double l=0,r=1e10;
while(l+eps<r){
double mid=(l+r)/2.0;
if(judge(mid)) r=mid;
else l=mid;
}
printf("%.10f\n",r);
}
return 0;
}
HDU 6582 Path (最短路+最小割)
题意:
给出有向图,删掉边使得1到n的最短路改变,删掉边的代价为该边的边权。求最小代价。
分析:
要将最短路改变,那么肯定在最短路上进行操作,很容易得到用最短路建图求个最小割就好了。最短路建图就是满足
的点。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll MAXN=1e4+10;
const ll MAXM=2e4+10;
const ll INF=0x3f3f3f3f3f3f3f3f;
struct Edge{
ll to,next,cap,flow;
}edge[MAXM];
ll tol,head[MAXN];
void init(){
tol=2;
memset(head,-1,sizeof(head));
}
void addedge(ll u,ll v,ll w,ll rw){
edge[tol].to=v;edge[tol].cap=w;edge[tol].flow=0;
edge[tol].next=head[u];head[u]=tol++;
edge[tol].to=u;edge[tol].cap=rw;edge[tol].flow=0;
edge[tol].next=head[v];head[v]=tol++;
}
ll Q[MAXN];
ll dep[MAXN],cur[MAXN],sta[MAXN];
bool bfs(ll s,ll t,ll n){
ll front=0,tail=0;
memset(dep,-1,sizeof(dep[0])*(n+1));
dep[s]=0;
Q[tail++]=s;
while(front<tail){
ll u=Q[front++];
for(ll i=head[u];i!=-1;i=edge[i].next){
ll v=edge[i].to;
if(edge[i].cap>edge[i].flow&&dep[v]==-1){
dep[v]=dep[u]+1;
if(v==t){
return true;
}
Q[tail++]=v;
}
}
}
return false;
}
ll dinic(ll s,ll t,ll n){
ll maxflow=0;
while(bfs(s,t,n)){
for(ll i=0;i<n;i++){
cur[i]=head[i];
}
ll u=s,tail=0;
while(cur[s]!=-1){
if(u==t){
ll tp=INF;
for(ll i=tail-1;i>=0;i--){
tp=min(tp,edge[sta[i]].cap-edge[sta[i]].flow);
}
maxflow+=tp;
for(ll i=tail-1;i>=0;i--){
edge[sta[i]].flow+=tp;
edge[sta[i]^1].flow-=tp;
if(edge[sta[i]].cap-edge[sta[i]].flow==0){
tail=i;
}
}
u=edge[sta[tail]^1].to;
}else if(cur[u]!=-1&&edge[cur[u]].cap>edge[cur[u]].flow&&dep[u]+1==dep[edge[cur[u]].to]){
sta[tail++]=cur[u];
u=edge[cur[u]].to;
}else{
while(u!=s&&cur[u]==-1){
u=edge[sta[--tail]^1].to;
}
cur[u]=edge[cur[u]].next;
}
}
}
return maxflow;
}
struct node{
ll v;
ll cost;
node(ll v,ll cost):v(v),cost(cost){}
};
vector<node> g[MAXN];
void addedge(ll u,ll v,ll w){
g[u].push_back(node(v,w));
}
bool vis[MAXN];
ll dis[MAXN];
void spfa(ll start){
memset(vis,0,sizeof vis);
memset(dis,INF,sizeof dis);
vis[start]=1;
dis[start]=0;
queue<ll> q;
q.push(start);
while(!q.empty()){
ll u=q.front();
q.pop();
vis[u]=false;
for(ll i=0;i<g[u].size();i++){
ll v=g[u][i].v;
ll w=g[u][i].cost;
if(dis[v]>dis[u]+w){
dis[v]=dis[u]+w;
if(!vis[v]){
vis[v]=true;
q.push(v);
}
}
}
}
}
int main(){
int T;
scanf("%d",&T);
while(T--){
int n,m;
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++){
g[i].clear();
}
init();
ll u,v,w;
while(m--){
scanf("%lld%lld%lld",&u,&v,&w);
addedge(u,v,w);
}
spfa(1);
for(int i=1;i<=n;i++){
u=i;
for(int j=0;j<g[i].size();j++){
v=g[i][j].v;
w=g[i][j].cost;
if(dis[u]+w==dis[v]){
addedge(u,v,w,0);
}
}
}
printf("%lld\n",dinic(1,n,n));
}
return 0;
}
HDU - 6586 String (序列自动机+贪心)
题意:
多组输入,每组第一行输入一个字符串s和一个数字k,接下来每行输入26行,每行两个数字代表在长度为k的子序列中,a~z在这个范围中出现。
分析:
先用序列自动机构造出某个位置后每个字母的个数,每个字母 的第一个位置。
然后每次贪心地加入最小的字符,加入的条件为当前字母加入后,后面的字符满足剩余的条件。
即剩余的字母Ai在不超Ri的情况下能构成k长度的串,剩余的字母Ai+已拿取字母Ai>=Li且满足Li所需的长度小于剩余可添加长度。
#include<bits/stdc++.h>
#define mm(a,b) memset(a,b,sizeof(a))
#define ACCELERATE (ios::sync_with_stdio(false),cin.tie(0))
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
#define PI acos(-1.0)
#define E exp(1.0)
//#define io
using namespace std;
const int inf=0x3f3f3f3f;
const int maxn=1e5+10;
int l[30],r[30];
char s[maxn],ans[maxn];
int k;
int nxt[maxn][30],cnt[maxn][30],num[30];
bool judge(int pos,int tot){
int len=0,dis=0;
for(int i=0;i<26;i++){
if(num[i]+cnt[pos][i]<l[i]) return false;
len+=num[i]+min(cnt[pos][i],r[i]-num[i]);
dis+=max(0,l[i]-num[i]);
}
if(len<k) return false;
if(dis>k-tot) return false;
return true;
}
int main(){
#ifdef io
freopen("in.txt","r",stdin);
freopen("out.txt","w",stdout);
#endif
while(scanf("%s%d",s+1,&k)!=EOF){
int len=strlen(s+1);
for(int i=0;i<26;i++){
scanf("%d%d",&l[i],&r[i]);
}
mm(nxt,-1);
mm(cnt,0);
for(int i=len-1;i>=0;i--){
for(int j=0;j<26;j++){
nxt[i][j]=nxt[i+1][j];
cnt[i][j]=cnt[i+1][j];
}
nxt[i][s[i+1]-'a']=i+1;
cnt[i][s[i+1]-'a']++;
}
int pos=0,tot=0;
bool ok=true;
mm(num,0);
while(pos<=len&&tot<k){
bool can=false;
for(int i=0;i<26;i++){
if(nxt[pos][i]!=-1&&num[i]<r[i]){
num[i]++;
if(judge(nxt[pos][i],tot+1)){
can=true;
ans[tot++]=i+'a';
pos=nxt[pos][i];
break;
}
num[i]--;
}
}
if(!can){
ok=false;
break;
}
}
ans[tot]='\0';
if(ok) printf("%s\n",ans);
else printf("-1\n");
}
return 0;
}