HIT训练营----1 题解
这次比赛地址在 http://acm.hust.edu.cn:8080/judge/contest/view.action?cid=16641#overview
Problem A |
签到题,注意这句话的意思就行了,Your are to output a sequence of values displayed by the device. The first number of the sequence is the maximal element of the first Minput numbers, the second number is the maximal element of the 2nd, …, M+1-st input numbers and so on.
再用单调递减队列保存前m项中的信息就行了
代码如下:
#include <iostream> #include <cstdio> #include <cstring> using namespace std; const int X = 25005; struct node{ int id,val; }q[X]; int main(){ //freopen("sum.in","r",stdin); int x,n; while(cin>>n){ int h = 0,t = 0; int id = 0; while(scanf("%d",&x),x!=-1){ id++; while(h<t&&q[h].id+n<=id) h++; while(h<t&&q[t-1].val<=x) t--; q[t].val = x; q[t].id = id; t++; if(id>=n) printf("%d\n",q[h].val); } } return 0; }
Problem B |
题目:
给出n个数,然后询问区间[l,r]中满足若数x出现过的次数为x的数的个数
分析:
由于题目n的范围为1e5,所以最多只有450个数左右符合条件,首先把不符合条件的删除,得到新的序列,然后可以用dp[i][j]先预处理出位置j中出现过了标号为i的数的个数
代码如下:
#include <cstdio> #include <cstring> #include <iostream> #include <vector> #include <algorithm> using namespace std; const int maxn = 500; const int maxm = 100005; int a[maxm],b[maxm],c[maxm][2],n,m; int dp[maxn][maxm]; vector<int> v; int main(){ //freopen("sum.in","r",stdin); while(cin>>n>>m){ for(int i=1;i<=n;i++){ scanf("%d",&a[i]); b[i] = a[i]; } sort(a+1,a+n+1); a[0] = -1; v.clear(); int cnt = 0; memset(c,0,sizeof(c)); for(int i=1;i<=n;i++){ if(a[i]!=a[i-1]) cnt++; c[cnt][0] = a[i]; c[cnt][1]++; } for(int i=1;i<=cnt;i++) if(c[i][1]>=c[i][0]) v.push_back(c[i][0]); int len = v.size(); memset(dp,0,sizeof(dp)); for(int i=0;i<len;i++){ for(int j=1;j<=n;j++) if(v[i]==b[j]) dp[i][j] = dp[i][j-1]+1; else dp[i][j] = dp[i][j-1]; } int x,y; while(m--){ scanf("%d%d",&x,&y); int ans = 0; for(int i=0;i<len;i++) ans += (dp[i][y]-dp[i][x-1])==v[i]; printf("%d\n",ans); } } return 0; }
Problem C |
题目:主要靠查代码的编程能力(目测是所有题中第二个简单题)
二维迷宫中,有一些着火点(可能不止一个),给出你的初始位置,人与着火点都是每秒钟向相邻的格子移动一格,问人能不能够在火势到达之前走到边界,如果能够,输出最短的时间
分析:
对于着火点,可以预处理把所有的着火点都放进队列中(若是对于每个着火点都用BFS扩展,必会TLE。。。),然后再用BFS处理出所有可燃烧的格子的最短到达时间。再用BFS来计算出人能够达到边界的最短时间。。。
我的代码略挫
#include <cstdio> #include <cstring> #include <iostream> using namespace std; const int X = 1005; int dirx[] = {-1,1,0,0}; int diry[] = {0,0,-1,1}; int n; int m; bool use[X][X]; int tt[X][X]; char map[X][X]; struct node{ int x,y,step; node(int _x,int _y,int _step){ x = _x; y = _y; step = _step; } }; struct point{ int x,y,step; point(){} point(int _x,int _y,int _step){ x = _x; y = _y; step = _step; } }q[X*X]; bool out(int x,int y){ return x>=n||y>=m||x<0||y<0; } int bfs(int sx,int sy){ memset(use,false,sizeof(use)); int head = 0; int tail = 0; q[tail++] = point(sx,sy,0); while(head<tail){ point pre = q[head++]; int x = pre.x; int y = pre.y; if(x==0||y==0||x==n-1||y==m-1) return pre.step; pre.step ++; for(int i=0;i<4;i++){ int dx = dirx[i]+x; int dy = diry[i]+y; if(out(dx,dy)) continue; if(use[dx][dy]||tt[dx][dy]<=pre.step||map[dx][dy]=='#') continue; use[dx][dy] = true; q[tail++] = point(dx,dy,pre.step); } } return -1; } void init(){ memset(use,false,sizeof(use)); int head = 0; int tail = 0; for(int i=0;i<n;i++) for(int j=0;j<m;j++) if(map[i][j]=='F') q[tail++] = point(i,j,0); while(head<tail){ point pre = q[head++]; int x = pre.x; int y = pre.y; tt[x][y] = min(tt[x][y],pre.step ++); for(int i=0;i<4;i++){ int dx = dirx[i]+x; int dy = diry[i]+y; if(out(dx,dy)) continue; if(use[dx][dy]||map[dx][dy]=='#') continue; use[dx][dy] = true; q[tail++] = point(dx,dy,pre.step); } } } void solve(){ cin>>n; cin>>m; for(int i=0;i<n;i++) scanf("%s",map[i]); memset(tt,0x3f,sizeof(tt)); int sx,sy; sx = sy = 0; for(int i=0;i<n;i++) for(int j=0;j<m;j++) if(map[i][j]=='J'){ sx = i; sy = j; } init(); int ok = bfs(sx,sy); if(ok==-1) puts("IMPOSSIBLE"); else printf("%d\n",ok+1); } int main(){ //freopen("sum.in","r",stdin); int ncase; cin>>ncase; while( ncase-- != 0 ) solve(); return 0; }
Problem D |
题目:给出一个文本串以及n个模式串,现在问存不存在从模式串中选择一些字母使得能够组成文本串,若能够的话,输出最小费用(每个模式串的每个字母的费用为第几个模式串)。
分析:
最小费用流,建立超级源点以及超级汇点,对于文本串,统计每个字母出现的次数,建立从源点到出现过的字母的有向边,费用为0,流量为该字母出现的次数。
再建立26个字母的中间节点。
对于模式串,建立n个节点,用来限制每个模式串的最大删除字母数目,对于每个模式串si,连上中间节点到他的有向边,费用为i,流量为该字母出现的次数。
然后让这n个节点分别连上汇点的有向边,流量为该模式串的出现次数,费用为0。
此致,费用流建图结束
(说的不是很好,具体看代码)
#include <iostream> #include <cstdio> #include <cstring> #include <queue> using namespace std; const int X = 1005; const int maxn = 100005; const int maxm = 3000005; const int inf = 1e9; #define debug puts("here"); int dis[maxn],pre[maxn],arc[maxn]; int po[maxn],tol; bool use[maxn]; int map[X][X]; int n,m,s,t,k,p,ans; int d[maxn]; int num[30]; struct node{ int y,f,c,next; }edge[maxm]; void add(int x,int y,int c,int f){ edge[++tol].y = y; edge[tol].c = c; edge[tol].f = f; edge[tol].next = po[x]; po[x] = tol; edge[++tol].y = x; edge[tol].c = -c; edge[tol].f = 0; edge[tol].next = po[y]; po[y] = tol; } bool spfa(){ memset(use,false,sizeof(use)); for(int i=1;i<=n;i++) dis[i] = inf; dis[s] = 0; queue<int> q; q.push(s); pre[s] = 0; int x,y; while(q.size()){ x = q.front(); q.pop(); use[x] = false; for(int i=po[x];i;i=edge[i].next){ y = edge[i].y; if(edge[i].f>0&&edge[i].c+dis[x]<dis[y]){ dis[y] = edge[i].c + dis[x]; pre[y] = i; if(!use[y]){ use[y] = true; q.push(y); } } } } if(dis[t]==inf) return false; int aug = inf; for(int i=pre[t];i;i=pre[edge[i^1].y]) aug = min(aug,edge[i].f); for(int i=pre[t];i;i=pre[edge[i^1].y]){ edge[i].f -= aug; edge[i^1].f += aug; } ans += dis[t]*aug; return true; } int main(){ //freopen("sum.in","r",stdin); char str[105]; while(cin>>str>>n){ memset(po,0,sizeof(po)); tol = 1; int sum = 0; memset(num,0,sizeof(num)); int len = strlen(str); for(int i=0;i<len;i++) num[str[i]-'a'+1] ++; s = 26+n+1; t = s+1; for(int i=1;i<=26;i++){ add(s,i,0,num[i]); sum += num[i]; } int x; for(int i=1;i<=n;i++){ scanf("%s%d",str,&x); memset(num,0,sizeof(num)); for(int j=0;str[j];j++) num[str[j]-'a'+1] ++; for(int j=1;j<=26;j++) if(num[j]) add(j,i+26,i,num[j]); add(i+26,t,0,x); } n = t; ans = 0; int ok = 0; while(spfa()) ; for(int i=po[t];i;i=edge[i].next) ok += edge[i].f; if(ok!=sum) puts("-1"); else cout<<ans<<endl; } return 0; }
Problem E |
题目:
经典图论模型,求某个点集,使得点集中任意两个点至少有一条可达边(u可到达v,或者v可以到达u),求点集的最大数目
分析:
强连通分量求GCC,然后构造出新图,新图是一个dag,对于dag上用dp求出最长路径即可。DP转移方程为dp[x] = size[x] + max(dp[y]); 缩点后有边x到y的边,记忆化搜索就行了,具体看实现代码
#include <iostream> #include <cstring> #include <cstdio> #include <vector> using namespace std; const int maxn = 1005; const int maxm = 50005; #define debug puts("here"); int dfn[maxn],low[maxn],stack[maxn],father[maxn],bcnt,top,depth; bool instack[maxn]; int po[maxn],tol,n,m; int id[maxn]; int dp[maxn]; int sum[maxn]; vector<int> vec[maxn]; struct node{ int y,next; }edge[maxm]; void add(int x,int y){ edge[++tol].y = y; edge[tol].next = po[x]; po[x] = tol; } void dfs(int x){ //递归实现tarjan算法 low[x] = dfn[x] = ++depth; instack[x] = true; stack[++top] = x; int y; for(int i=po[x];i;i=edge[i].next){ y = edge[i].y; if(!dfn[y]){ dfs(y); low[x] = min(low[x],low[y]); } else if(instack[y]) low[x] = min(low[x],dfn[y]); } if(low[x]==dfn[x]){ ++bcnt; do{ y = stack[top--]; instack[y] = false; father[y] = bcnt; }while(x!=y); } } void tarjan(){ memset(low,0,sizeof(low)); memset(dfn,0,sizeof(dfn)); top = bcnt = depth = 0; for(int i=1;i<=n;i++) if(!dfn[i]) dfs(i); } int f(int x){ //记忆化方法求dag上的最长路径 if(dp[x]) return dp[x]; int ans = 0; for(int i=0;i<(int)vec[x].size();i++){ //从x的所有边出发,求出最大的路径 int y = vec[x][i]; ans = max(ans,f(y)); //转移方程 } dp[x] = ans+sum[x]; return dp[x]; } void dag(){ memset(id,0,sizeof(id)); memset(sum,0,sizeof(sum)); memset(dp,0,sizeof(dp)); for(int i=1;i<=n;i++) vec[i].clear(); for(int x=1;x<=n;x++){ //构造新图 for(int j=po[x];j;j=edge[j].next){ int y = edge[j].y; if(father[x]!=father[y]){ vec[father[x]].push_back(father[y]); id[father[y]] ++; } } sum[father[x]] ++; //统计每个缩点后的该节点所包含的所有原图的节点数目 } int ans = 0; for(int i=1;i<=bcnt;i++) if(!id[i]) ans = max(f(i),ans); cout<<ans<<endl; } int main(){ //freopen("sum.in","r",stdin); int ncase; cin>>ncase; while(ncase--){ cin>>n>>m; int x,y; memset(po,0,sizeof(po)); tol = 0; while(m--){ scanf("%d%d",&x,&y); add(x,y); } tarjan(); dag(); } return 0; }