2016 acm/icpc 青島網路賽 題解(hdu 5878-5889,9道題)
5878.I Count Two Three(打表預處理,二分)
題目大意:有一些數可以寫成
題目分析:打表預處理,發現1e9範圍內的那種數只有約6000個,都列出來,然後排個序,lower_bound二分查詢之。
在這裡順便總結下吧,lower_bound(start,end,n)
和upper_bound(start,end,n)
,還有binary_search(start,end,n)
的區別如下:
2016.11.16 cmershen 修正:
1. lower_bound演算法返回一個非遞減序列[first, last)中的第一個大於等於值val的位置。
2. upper_bound演算法返回一個非遞減序列[first, last)中第一個大於val的位置。
3. binary_search返回true/false。
by the way,第三個函式應該很少會有人用吧~~
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
ll num[6000];
int t,n;
int pre() {
int i=0;
ll cur=1 ;
//2^30,3^19,5^13,7^11 > 1e9
for(int a=0;a<=30;a++) {
for(int b=0;b<=19;b++) {
for(int c=0;c<=13;c++) {
for(int d=0;d<=11;d++) {
cur=pow(2,a)*pow(3,b);
if(cur>1e9) break;
cur*=pow(5,c);
if (cur>1e9) break;
cur*=pow(7,d);
if(cur>1e9) break;
num[i++]=cur;
}
}
}
}
sort(num,num+i);
return i;
}
int main() {
int i=pre();
scanf("%d",&t);
while(t--) {
scanf("%d",&n);
int p=lower_bound(num,num+i,n)-num;
printf("%d\n", num[p]);
}
}
5879.Cure(高等數學題)
題目大意:輸入n,求
題目分析:我們在高數中學過,(啥時候學的我怎麼不記得了,捂臉)那個和收斂於
這裡有個坑,就是題目中沒說n多大, 所以是任意大的,要用字串讀入。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
double f[110300];
char s[1111];
void pre() {
f[0]=0.0;
f[1]=1.0;
ll i=2;
while(i<110300) {
f[i]=f[i-1]+1.0/(i*i);
i++;
}
}
int main() {
pre();
while(scanf("%s",s)!=EOF) {
if(strlen(s)>=7) {
printf("1.64493\n");
}
else {
int a;
sscanf(s,"%d",&a);
if(a>=110293)
printf("1.64493\n");
else
printf("%.5lf\n", f[a]);
}
}
}
5880.Family View(ac自動機)
題目大意:
給出一些“和諧單詞”,和一段文章,將和諧單詞用等長星號代替,忽略大小寫。
題目分析:
一眼就看出來是ac自動機,模板題。下面的程式碼可以當做模板用,若註釋第70行則不忽略大小寫。
/*
*@author:Dan__ge (modified by cmershen)
*@description:AC自動機模板,輸入和諧字典,和待匹配的字串,把字串都和諧成星號,去掉70行的註釋則變成大小寫不敏感!!!
*@source:hdu 5880
*/
#include <queue>
#include <stdio.h>
#include <string.h>
#include <iostream>
#include <algorithm>
using namespace std;
const int maxn=1000010;
int ans[maxn],cnt[maxn];
struct Trie{
int next[maxn][26],fail[maxn],end[maxn];
int root,L,ko;
int newnode(){
for(int i = 0;i < 26;i++)
next[L][i] = -1;
end[L++] = 0;
return L-1;
}
void init(){
L = 0,ko=0;
root = newnode();
memset(ans,0,sizeof(ans));
}
void insert(char buf[]){
int len = strlen(buf);
int now = root;
for(int i = 0;i < len;i++){
if(next[now][buf[i]-'a'] == -1)
next[now][buf[i]-'a'] = newnode();
now = next[now][buf[i]-'a'];
}
if(end[now]==0){
ko++;end[now]=ko;
cnt[ko]=len;
}else{
if(cnt[end[now]]<len) cnt[end[now]]=len;
}
}
void build(){
queue<int>Q;
fail[root] = root;
for(int i = 0;i < 26;i++)
if(next[root][i] == -1) next[root][i] = root;
else{
fail[next[root][i]] = root;
Q.push(next[root][i]);
}
while( !Q.empty() ){
int now = Q.front();
Q.pop();
for(int i = 0;i < 26;i++)
if(next[now][i] == -1) next[now][i] = next[fail[now]][i];
else{
fail[next[now][i]]=next[fail[now]][i];
Q.push(next[now][i]);
}
}
}
void query(char buf[]){
int len = strlen(buf);
int now = root;
int res = 0;
for(int i = 0;i < len;i++){
if(buf[i]>='a'&&buf[i]<='z') now = next[now][buf[i]-'a'];
else if(buf[i]>='A'&&buf[i]<='Z') now = next[now][buf[i]-'A'];
else continue;
int temp = now;
while( temp != root ){
ans[i+1]--;ans[i-cnt[end[temp]]+1]++;
temp = fail[temp];
}
}
}
};
char buf[maxn];
Trie ac;
int main(){
int T,n;
scanf("%d",&T);
while( T-- ){
scanf("%d",&n);
ac.init();
for(int i = 0;i < n;i++){
scanf("%s",buf);
ac.insert(buf);
}
ac.build();
getchar();
gets(buf);
ac.query(buf);
int len=strlen(buf),sum=0;
for(int i=0;i<len;i++){
sum+=ans[i];
if(sum<=0) printf("%c",buf[i]);
else printf("*");
}
printf("\n");
}
return 0;
}
5881.Tea(貪心)
題目大意:給你一壺茶,你不知道茶有多少,只知道在[L,R]之間,給你兩個杯子,要求兩個杯子的茶水量差不超過1,杯子最後剩餘的水不超過1,問至少倒幾次。
題目分析:這題我有點不太理解,就直接貼下答案吧,因為他說至少倒幾次是在[L,R]的最壞條件下還是最好條件下?如果考慮最好條件下,那麼R值就沒用了,倒兩次就好了。還有就是你是不是隻知道茶水什麼時候剩1以下?
這道題的解釋是這樣的:
* 如果
* 如果
* 如果
* 否則,先倒兩次,分別是
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
ll l,r;
int main() {
while(scanf("%I64d %I64d",&l,&r)!=EOF) {
ll ans;
if(r<=1)
ans=0;
else if(r<=2)
ans=1;
else if(r-l<=1)
ans=2;
else {
if(l<=1) l=1;
ans=(r-l)/2+1;
}
printf("%I64d\n", ans);
}
}
5882.Balanced Game(水題)
題目大意:
問n個手勢的石頭剪子布是否公平?
題目分析:
說實話這題我也沒徹底理解,因為從直覺上看,就是奇數公平偶數不公平,而且偶數不公平顯然如此。
but,如何證明奇數一定公平?有沒有一種對任意n均公平的分配方案?這個有待進一步探討啊~~
2016.11.16 cmershen add
知乎上有大神回覆了,可以這樣設計方案:
在圓周上等距離分佈2n+1個點,對任意一個點,順時針數n個點贏他,逆時針數n個點輸他,這樣就可以保證任意兩種不同手勢均能分出勝負,且完全公平。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int t,n;
int main() {
scanf("%d",&t);
while (t--) {
scanf("%d",&n);
if (n&1)
printf("Balanced\n");
else
printf("Bad\n");
}
}
5883.The Best Path(歐拉回路)
題目大意:n個點,m個邊的無向圖,每個點有權值
題目分析:
求尤拉路咯,小學奧數都學過,看有幾個度為奇數的點。如果是0個,則是歐拉回路,如果是2個,則是尤拉通路。
接下來就看每個點要經過幾次:
如果度為n,那麼這n條邊都要遍歷一次,而每走其中兩條邊,就要經過這個點一次(因為要來到這個點,還得出去),如果剩下一條邊,那麼也要走一次這個點,因為它必是起點/終點。
根據異或的性質,如果這個次數值是奇數,那就加到答案裡,如果是偶數,那就消掉了。
那麼如果該圖存在的是尤拉通路,那答案就是唯一的,如果是歐拉回路,相當於起點多走了一次,列舉起點即可。
這題的資料我覺得有點弱,因為還需要考慮整個圖是否連通的問題,但是沒考慮也過了。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int t,n,m,u,v;
short a[100005];
int deg[100005];
vector<int> g[100005];
int main() {
scanf("%d",&t);
while (t--) {
scanf("%d %d",&n,&m);
for(int i=1;i<=n;i++)
g[i].clear();
memset(deg,0,sizeof(deg));
for(int i=1;i<=n;i++)
scanf("%d",&a[i]);
for(int i=0;i<m;i++) {
scanf("%d %d",&u,&v);
g[u].push_back(v);
g[v].push_back(u);
deg[u]++;deg[v]++;
}
int cnt=0,sum=0;
for(int i=1;i<=n;i++) {
if(deg[i]&1)
cnt++;
int temp=(deg[i]+1)/2;
if(temp & 1)
sum^=a[i];
}
if(cnt==0) { //Eular Curcuit
int ans=0;
for(int i=1;i<=n;i++)
ans=max(ans,sum^a[i]);
printf("%d\n", ans);
}
else if(cnt==2) //Eular Path
printf("%d\n", sum);
else
printf("Impossible\n");
}
}
5884. sort(huffman樹)
題目大意:
給你n個正整數和最大花費T,每次可以合併不超過k個數,花費是這k個數的和,最後合併成一個數,求不超過花費T的情況下,最小的k。
題目分析:
其實這就是一個k叉Huffman樹求帶權路徑長度的問題。首先考慮到我們要用k-1次操作合併n-1個數,那麼當
接下來就是求這個最小的k,因為k的取值範圍確定為
如果你就這麼做下去,那麼你肯定tle了。
這題卡了logn(大概在6左右),直接用優先佇列的複雜度是
用兩個佇列q1和q2,其中q1裝原來的數(排序好),q2裝合併後的數,根據Huffman樹性質,合併數肯定是越合越大,所以q2也是單調的。那麼每次先從q1和q2里加一起取k個數,合併起來扔q2裡,直到q1取完,再取q2,直到q2裡剩一個數。這樣做就能過了,時間在1s左右。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int t0,t,n;
int a[100005];
int cost(int x) {
queue<int> q1,q2;
int ans=0;
sort(a,a+n);
for(int i=0;i<n;i++)
q1.push(a[i]);
if((n-1)%(x-1)!=0) {
int temp=(n-1)%(x-1)+1;
int sum=0;
for(int j=0;j<temp;j++) {
sum+=q1.front();
q1.pop();
}
q2.push(sum);
ans+=sum;
}
while(!q1.empty()) {
int sum=0;
for(int j=0;j<x;j++) {
if(!q1.empty() && !q2.empty()) {
if(q1.front()<q2.front()) {
sum+=q1.front();
q1.pop();
}
else {
sum+=q2.front();
q2.pop();
}
}
else if(q1.empty()) {
sum+=q2.front();
q2.pop();
}
else if(q2.empty()) {
sum+=q1.front();
q1.pop();
}
}
ans+=sum;
q2.push(sum);
}
while(q2.size()>1) {
int sum=0;
for(int i=0;i<x;i++) {
ans+=q2.front();
q2.pop();
}
ans+=sum;
q2.push(ans);
}
return ans;
}
int main() {
scanf("%d",&t0);
while(t0--) {
scanf("%d %d",&n,&t);
for(int i=0;i<n;i++) {
scanf("%d",&a[i]);
}
int l=2,r=n;
while(l<r) {
int mid=(l+r)/2;
if(cost(mid)<=t)
r=mid;
else
l=mid+1;
}
printf("%d\n", l);
}
}
5887. Herbs Gathering(dfs解01揹包+剪枝)
題目大意:
01揹包,100個物品,體積和揹包容量可達1e9。
題目分析:
我們傳統的用DP解揹包都是物品多,揹包容量M有限,這樣可以用DP做,在
但此題的M太大了,N又很小,所以用搜索來解。但
這道題我見到的黑科技很多,最極端的是用時間剪枝,就是一個dfs跑到10ms就return,居然也能過。。。。。。。。
我採用的是按價效比剪枝,也就是先按價效比排序,如果揹包剩下的空間裡全裝這個東西也沒有更優解,則停止搜尋(因為後面的價效比低,就更不可能找到解了),這樣就0ms AC了。
#include <bits/stdc++.h>
using namespace std;
#define RE(x) freopen(x,"r",stdin)
#define WR(x) freopen(x,"w",stdout)
typedef long long ll;
int n,t;
typedef struct {
int t;//time
int s;//score
double r;//rate
}herb;
herb h[105];
bool cmp(herb x,herb y) {
return x.r>y.r;
}
ll ans;
void dfs(int i,ll cur,ll sc) { //決定第i個物品放不放,當前揹包容量為cur,得分為sc
if(sc>ans)
ans=sc;
if(sc+h[i].r*(t-cur)<ans) return;//剩下的容積就算全放i,也得不到更優解,更何況後面的價效比更低呢
if(i<n) {
if(h[i].t<=t-cur) //第i個物品裝得下
dfs(i+1,cur+h[i].t,sc+h[i].s);
dfs(i+1,cur,sc);//不放i
}
}
int main() {
RE("in.txt");
WR("out.txt");
while(scanf("%d %d",&n,&t)!=EOF) {
for(int i=0;i<n;i++) {
scanf("%d %d",&h[i].t,&h[i].s);
h[i].r=(double)(h[i].s)/(double)(h[i].t);
}
ans=0;
sort(h,h+n,cmp);
dfs(0,0,0);//決定第0個物品,目前揹包裡容量為0,得分為0
printf("%I64d\n", ans);
}
}
5889.Barricade(最短路徑+網路流)
題目大意:
有個地圖,n個點,m個邊,無向圖,每條邊的長度都一樣。
你在1點,敵人在n點,已知敵人一定按最短路線來找你,所以你要將敵人的路設定障礙,使得敵人不管沿什麼路徑過來,都會遇到你的障礙。又已知在第i條邊設定障礙需要cost[i]的花費,求至少多少花費才能使敵人全部遇到障礙。
題目分析:
首先在原圖上跑一遍dijkstra演算法,求1點到其他點的最短路,然後構造一張新圖,在最短路的路徑上(條件是dis[i]-dis[j]==1 && i,j有邊
)加邊,邊權為設定障礙的花費。然後新圖上以1為源,n為匯點跑最大流即可。
這裡分別給出dinic演算法和SAP演算法的AC程式碼,供當做模板用,其中sap演算法用時15ms,dinic演算法用時46ms。
//sap,15ms
#include <bits/stdc++.h>
using namespace std;
#define RE(x) freopen(x,"r",stdin)
#define WR(x) freopen(x,"w",stdout)
typedef long long ll;
#define INF 0x3f3f3f3f
int t,u,v,w,n,m;
int cost[1005][1005];
int dis[1005];
struct EDGE {
int u,v,cap;
int next;
}edge[20005];
int head[1005],p;
int gap[1005],dep[1005],cur[1005],stk[1005];
void addedge(int u,int v) {
int c=cost[u][v];
edge[p].u=u;edge[p].v=v;edge[p].cap=c;
edge[p].next=head[u]; head[u]=p++;
edge[p].u=v;edge[p].v=u;edge[p].cap=0;
edge[p].next=head[v]; head[v]=p++;
}
void dijkstra(int u) { //u點為源,求單源最短路徑
bool vis[1005];
for(int i=1;i<=n;i++) {
if(cost[i][u]!=INF && i!=u)
dis[i]=1;
else
dis[i]=INF;
vis[i]=false;
}
dis[u]=0;
vis[u]=true;
for(int i=1;i<=n;i++) {
int min=INF;
int x;
for(int j=1;j<=n;j++) {
if(!vis[j] && dis[j]<min) {
min=dis[j];
x=j;
}
}
vis[x]=true;
for(int j=1;j<=n;j++)
if(!vis[j] && cost[x][j]!=INF && min+1<dis[j])
dis[j]=min+1;
}
}
void bfs(int t) {
memset(dep,-1,sizeof(dep));
memset(gap,0,sizeof(gap));
queue<int> q;
dep[t]=0;
gap[0]=1;
q.push(t);
while(!q.empty()) {
int u=q.front();
q.pop();
for(int i=head[u];i!=-1;i=edge[i].next) {
int v=edge[i].v;
if(edge[i^1].cap>0 && dep[v]==-1) {
q.push(v);
dep[v]=dep[u]+1;
gap[dep[v]]++;
}
}
}
}
int sap(int s,int t) {
bfs(t);
memcpy(cur,head,sizeof(cur));
int ans=0;
int u=s,top=0,i;
while(dep[s]<n) {
if(u==t) {
int delta=INF;
int flag=n;
for(i=0;i!=top;i++) {
if(delta>edge[stk[i]].cap) {
delta=edge[stk[i]].cap;
flag=i;
}
}
for(i=0;i!=top;i++) {
edge[stk[i]].cap-=delta;
edge[stk[i]^1].cap+=delta;
}
ans+=delta;
top=flag;
u=edge[stk[top]].u;
}
for(i=cur[u];i!=-1;i=edge[i].next) {
int v=edge[i].v;
if(edge[i].cap>0 && dep[u]==dep[v]+1)
break;
}
if(i!=-1) {
cur[u]=i;
stk[top++]=i;
u=edge[i].v;
}
else {
if(--gap[dep[u]]==0)
break;
int mind=n+1;
for(i=head[u];i!=-1;i=edge[i].next) {
if(edge[i].cap>0 && mind>dep[edge[i].v]) {
mind=dep[edge[i].v];
cur[u]=i;
}
}
dep[u]=mind+1;
gap[dep[u]]++;
u=(u==s)?u:edge[stk[--top]].u;
}
}
return ans;
}
int main() {
scanf("%d",&t);
while(t--) {
scanf("%d %d",&n,&m);
memset(cost,0x3f,sizeof(cost));
memset(head,-1,sizeof(head));
p=0;
for(int i=1;i<=m;i++) {
scanf("%d %d %d",&u,&v,&w);
cost[u][v]=w;
cost[v][u]=w;
}
dijkstra(1);
for(int i=1;i<=n;i++) {
for(int j=1;j<=n;j++) {
if(dis[i]+1==dis[j] && cost[i][j]!=INF)
addedge(i,j);
}
}
printf("%d\n", sap(1,n)); //源點1,匯點n,求最大流
}
}
//Dinic,46ms
#include <bits/stdc++.h>
using namespace std;
#define RE(x) freopen(x,"r",stdin)
#define WR(x) freopen(x,"w",stdout)
typedef long long ll;
#define INF 0x3f3f3f3f
int t,u,v,w,n,m;
int cost[1005][1005];
int dis[1005];
struct EDGE {
int to,next,flow;
}edge[20005];
int head[10005],p,level[10005];
void addedge(int u,int v) {
int w=cost[u][v];
edge[p].to=v;
edge[p].flow=w;
edge[p].next=head[u];
head[u]=p;p++;
edge[p].to=u;
edge[p].flow=0;
edge[p].next=head[v];
head[v]=p;p++;
}
void dijkstra(int u) {