【loli的胡策】高一信心場11.2(bitset+dp+二分+樹形揹包)
1、比賽(contest)
N(1 <= N <= 100)個同學按1..N依次編號參加羽毛球比賽。每人的實力不盡相同,不存在兩個人實力相同的情況。
比賽分成若干輪進行,每一輪是兩個人對決。如果編號為A的同學的實力強於編號為B的同學(1 <= A <= N; 1 <= B <= N; A != B),那麼她們的對決中,編號為A的同學總是能勝出。
你為了知道實力的具體排名,於是你找來M(1 <= M <= 4,500)輪比賽的結果,希望能根據這些資訊,推斷出儘可能多的同學得實力排名。比賽結果保證不會自相矛盾。
輸入格式:
第1行:用空格隔開的2個整數:N 和 M
第2..M+1行: 每行2個整數A、B(用空格隔開的),描述一輪比賽,每行的第一個編號為勝者
輸入樣例:
5 5
4 3
4 2
3 2
1 2
2 5
輸出格式:
1個整數,表示排名可確定的同學的數目
輸出樣例:
2
輸出說明:
編號為2的同學的排名為第4,編號為5的同學排名最後。其他3人的排名仍無法確定。
題解:
啊這個題一眼秒?top原圖+逆圖一波然後bitset亂搞?
程式碼:
#include <queue>
#include <bitset>
#include <cstdio>
#include <cstring>
#define N 105
#define M 4505
using namespace std;
int n,m,i;
bitset<105>in[N],out[N];bool vis[N];
int tot,nxt[M],point[N],v[M],tot1,nxt1[M],point1[N],v1[M],du[N],du1[N];
void addline(int x,int y){++tot; nxt[tot]=point[x]; point[x]=tot; v[tot]=y;}
void addline1(int x,int y){++tot1; nxt1[tot1]=point1[x]; point1[x]=tot1; v1[tot1]=y;}
void top()
{
int i;
queue<int>q;
for (i=1;i<=n;i++)
{
if (!du[i]) q.push(i);
in[i][i]=1;
}
while (!q.empty())
{
int now=q.front(); q.pop();
vis[now]=1;
for (int i=point[now];i;i=nxt[i])
{
in[v[i]]|=in[now];
du[v[i]]--;
if (!du[v[i]] && !vis[v[i]]) vis[v[i]]=1,q.push(v[i]);
}
}
memset(vis,0,sizeof(vis));
for (i=1;i<=n;i++)
{
if (!du1[i]) q.push(i);
out[i][i]=1;
}
while (!q.empty())
{
int now=q.front(); q.pop();
vis[now]=1;
for (int i=point1[now];i;i=nxt1[i])
{
out[v1[i]]|=out[now];
du1[v1[i]]--;
if (!du1[v1[i]] && !vis[v1[i]]) vis[v1[i]]=1,q.push(v1[i]);
}
}
}
int main()
{
freopen("contest.in","r",stdin);
freopen("contest.out","w",stdout);
scanf("%d%d",&n,&m);
for (i=1;i<=m;i++)
{
int x,y;
scanf("%d%d",&x,&y);
addline(x,y);du[y]++;
addline1(y,x);du1[x]++;
}
top();
int ans=0;
for (i=1;i<=n;i++)
if (in[i].count()+out[i].count()-1==n) ans++;
printf("%d",ans);
}
2、運動計劃(run)
地鼠誤入一個隧道它必須在N(1 <= N <= 10,000)分鐘內離開,否則隧道就會被人注滿水,在每分鐘的開始,地鼠會選擇下一分鐘是用來爬行還是休息。 地鼠的體力限制了她爬行的距離。更具體地,如果地鼠選擇在第i分鐘內爬行,她可以在這一分鐘內爬可k_i(1 <= k_i <= 1,000)米,並且她的絕望指數會增加1。不過,無論何時地鼠的絕望指數都不能超過M(1 <= M <= 500)。如果地鼠選擇休息,那麼她的絕望指數就會每分鐘減少1,但她必須休息到絕望指數恢復到0為止。在絕望指數為0時休息的話,絕望指數不會再變動。逃離開始時,地鼠的絕望指數為0。還有,在N分鐘的逃離結束時,地鼠的絕望指數也必須恢復到0,否則她將對自己的一生失去信心。請你計算一下,地鼠最多能爬多少米。(已知地鼠爬行距離達到最大時恰好能逃離隧道。
輸入格式:
第1行: 2個整數:N 和 M(用空格隔開)
第2。。N+1行: 第i+1行為1個整數:k_i
輸入樣例 (run.in):
5 2
5
3
4
2
10
輸出格式:
輸出一個整數,表示地鼠能爬行的最大距離
輸出樣例 (run.out):
9
輸出說明:
第1分鐘內爬行,在第2分鐘內休息,在第3分鐘內爬行,剩餘時間用來休息。
題解:
以前好像做過這道題
第一次寫掛了20pts?被學弟踩
儘量避免讓狀態向未來轉移啊!
dp的狀態很好設計:dp[i][j]表示時間i絕望值為j的最遠距離
程式碼:
#include <cstdio>
#include <iostream>
using namespace std;
int f[10005][505],k[10005];
int main()
{
freopen("run.in","r",stdin);
freopen("run.out","w",stdout);
int n,m,i,j,l;
scanf("%d%d",&n,&m);
for (i=1;i<=n;i++) scanf("%d",&k[i]);
for (i=1;i<=n;i++)
{
f[i][0]=max(f[i][0],f[i-1][0]);
for (j=m;j>=1;j--)
{
f[i][j]=max(f[i][j],f[i-1][j-1]+k[i]);
if (i>j) f[i][0]=max(f[i][0],f[i-j][j]);
}
}
printf("%d",f[n][0]);
}
3、找路徑(road)
圖上有N(1 <= N <= 1,000)個點,一共m(1 <= m<= 10,000)條邊。 第i條邊的端點為s_i、t_i,長度為L_i (1 <= L_i <= 1,000,000)。資料中保證每對{s_i,t_i}最多隻出現1次。你的任務是找一條將點1和點N連起來的路徑,如果存在不超過k(0=< k<=n)條邊的路徑則輸出0。否則輸出第k+1大的邊的最小值.
輸入格式:
第1行: 3個整數:N m K(用空格隔開)
第2..m+1行: 第i+1行為3個整數:s_i t_i L_i(用空格隔開)
輸入樣例
5 7 1
1 2 5
3 1 4
2 4 8
3 2 3
5 2 9
3 4 7
4 5 6
輸出格式:
1個整數
輸出樣例
4
輸出說明:
路徑為:1->3->2->5
題解:
這個一開始以為是原題,只不過那個題讓求最小距離,這個讓求最大值的最小值
熟悉不?二分!
可以轉化為最短路,比mid長的長度設為1,其他設為0,如果最短路小於等於k,說明這個值ok啊
題目沒說雙向邊差評!題目不給不連通-1差評!
程式碼:
#include <queue>
#include <cstdio>
#include <cstring>
#include <iostream>
#define N 1005
#define M 30005
#define INF 1e9
using namespace std;
struct hh{int maxx,k;};
int tot,nxt[M],point[N],v[M],c[M],dis[N],k,n,m;
bool vis[N],vv=0;
void addline(int x,int y,int z)
{
++tot; nxt[tot]=point[x]; point[x]=tot; v[tot]=y; c[tot]=z;
++tot; nxt[tot]=point[y]; point[y]=tot; v[tot]=x; c[tot]=z;
}
void spfa()
{
queue<int>q;
q.push(1);
memset(dis,0x7f,sizeof(dis));
memset(vis,0,sizeof(vis));
dis[1]=0;
while (!q.empty())
{
int now=q.front();q.pop();
for (int i=point[now];i;i=nxt[i])
if (dis[v[i]]>dis[now]+1)
{
dis[v[i]]=dis[now]+1;
if (!vis[v[i]]) vis[v[i]]=1,q.push(v[i]);
}
}
if (dis[n]>INF) printf("-1"),vv=1;
else if (dis[n]<=k) printf("0"),vv=1;
}
bool check(int mid)
{
queue<int>q;
q.push(1);
memset(dis,0x7f,sizeof(dis));
memset(vis,0,sizeof(vis));
dis[1]=0;
while (!q.empty())
{
int now=q.front();q.pop();vis[now]=0;
for (int i=point[now];i;i=nxt[i])
if (c[i]<=mid)
{
if (dis[v[i]]>dis[now])
{
dis[v[i]]=dis[now];
if (!vis[v[i]]) vis[v[i]]=1,q.push(v[i]);
}
}
else
if (dis[v[i]]>dis[now]+1)
{
dis[v[i]]=dis[now]+1;
if (!vis[v[i]]) vis[v[i]]=1,q.push(v[i]);
}
}
if (dis[n]>k) return 0;
return 1;
}
int main()
{
freopen("road.in","r",stdin);
freopen("road.out","w",stdout);
int l=INF,r=0;
scanf("%d%d%d",&n,&m,&k);
for (int i=1;i<=m;i++)
{
int x,y,z;
scanf("%d%d%d",&x,&y,&z);
addline(x,y,z);r=max(r,z);l=min(l,z);
}
spfa();
if (vv) return 0;
int ans=0;
while (l<=r)
{
int mid=(l+r)>>1;
if (check(mid)) ans=mid,r=mid-1;
else l=mid+1;
}
printf("%d",ans);
}
4、道路(roads)
有一棵樹,有m(1到150)個節點,如果砍掉幾條邊這棵樹就會變成多棵樹,求最少砍掉幾條邊可以在得到的樹中至少有一顆節點個數為n(1到m)。
輸入說明:
第1行 m n , 第2到第m行,每行兩個整數a 和b,表示a是b的父結點(1號點為根節點)。
輸出說明:
一個數為最少砍掉的邊數。
輸入樣例:
11 6
1 2
1 3
1 4
1 5
2 6
2 7
2 8
4 9
4 10
4 11
輸出樣例:
2
樣例說明:
砍掉1—4和1—5。
題解:
好像是原題?
f[i][j]表示以i為根節點分離出來j個點的最少切割數量
程式碼:
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define N 200
using namespace std;
const int INF=1e9;
int tot,nxt[N*2],point[N],v[N*2],size[N],f[N][N],n,ans,m,du[N];
void addline(int x,int y)
{
++tot; nxt[tot]=point[x]; point[x]=tot; v[tot]=y;
++tot; nxt[tot]=point[y]; point[y]=tot; v[tot]=x;
}
void treedp(int x,int fa)
{
f[x][1]=du[x];
for (int i=point[x];i;i=nxt[i])
if (v[i]!=fa)
{
treedp(v[i],x);
for (int j=m;j>=1;j--)
for (int k=1;k<=j;k++)
f[x][j]=min(f[x][j],f[v[i]][k]+f[x][j-k]-2);
}
ans=min(ans,f[x][m]);
}
int main()
{
freopen("roads.in","r",stdin);
freopen("roads.out","w",stdout);
scanf("%d%d",&n,&m);
for (int i=1;i<n;i++)
{
int x,y;
scanf("%d%d",&x,&y);
addline(x,y);du[x]++;du[y]++;
}
memset(f,0x3f,sizeof(f));
ans=INF;
treedp(1,0);
printf("%d",ans);
}