Codeforces Round #178 (Div. 2) :===> DP(01背包类) + 组和数学 + dfs
A:
直接模拟左边上去多少右边下来多少就行。
B:
题意:
每本书都有一个厚度ti,宽度wi,每本书的高度相同,给出n本书,让你排在一起是的所栈空间最小。竖着放,或者横着放一层。如图:
开始想着贪心,发现不对,一看数据量n<=100,那肯定是dp了。每本书要么竖着放,要么横着放,有点01背包的放与不放的思想。dp[i][j]表示对于第i本书,还有j个横着的空间可以放书的情况下,最小的占用空间。
dp[i][j] = min(dp[i - 1][j],dp[i - 1][j + ti + wi] - ti)
#include <iostream> #include <cstdio> #include <cmath> #include <vector> #include <cstring> #include <algorithm> #include <string> #include <set> #include <functional> #include <numeric> #include <sstream> #include <stack> #include <map> #include <queue> #define CL(arr, val) memset(arr, val, sizeof(arr)) #define lc l,m,rt<<1 #define rc m + 1,r,rt<<1|1 #define pi acos(-1.0) #define ll __int64 #define L(x) (x) << 1 #define R(x) (x) << 1 | 1 #define MID(l, r) (l + r) >> 1 #define Min(x, y) (x) < (y) ? (x) : (y) #define Max(x, y) (x) < (y) ? (y) : (x) #define E(x) (1 << (x)) #define iabs(x) (x) < 0 ? -(x) : (x) #define OUT(x) printf("%I64d\n", x) #define lowbit(x) (x)&(-x) #define Read() freopen("din.txt", "r", stdin) #define Write() freopen("dout.txt", "w", stdout); #define tr(container, it) for(typeof(container.begin()) it = container.begin(); it != container.end(); it++) #define M 35344 #define N 108 using namespace std; struct node { int ti,wi; }nd[N]; int dp[N][10007]; int main() { // Read(); int i,j,k; int n,m; int sum = 0; scanf("%d",&n); for (i = 1; i <= n; ++i) { scanf("%d%d",&nd[i].ti,&nd[i].wi); sum += nd[i].ti; } for (i = 0; i <= n; ++i) { for (j = 0; j <= sum; ++j) { dp[i][j] = sum; } } for (i = 1; i <= n; ++i) { for (j = 0; j <= sum; ++j) { dp[i][j] = min(dp[i][j],dp[i - 1][j]); if (j + nd[i].ti + nd[i].wi <= sum) { dp[i][j] = min(dp[i][j],dp[i - 1][j + nd[i].ti + nd[i].wi] - nd[i].ti); } } // for (j = 0; j <= sum; ++j) // printf("%d ",dp[i][j]); // printf("\n*************\n"); } int ans = 0; for (i = 0; i <= sum; ++i) { // printf("%d\n",dp[n][i]); if (ans == 0 || (ans > dp[n][i])) ans = dp[n][i]; } printf("%d\n",ans); return 0; }
C:
题意:
给你n个灯泡,这n个灯泡中有随机的m个是亮着的,接下来我们只能打开一个不亮的灯泡,该灯泡必须满足其左边或者右边有一个亮着的才可。问我们如果想打开所有的灯泡。问一共有多少种打开的方法。
思路:
排列组和真的很恶心。首先对于m个亮着的灯来说第一个的左边的灯的打开的顺序是不变的肯定是从右向左的,最后一个灯m的右边打开的顺序也是不变的肯定是从左到右的。然后在分析中间的m-1段,每一段的存在的迅速为2^(a[i] - 1)中其中a[i]表示第i段中存在多少个不亮的等。然后我们就利用前一个序列插入后一个序列的方法求出所有的可能的亮的顺序即可。
#include <iostream> #include <cstdio> #include <cmath> #include <vector> #include <cstring> #include <algorithm> #include <string> #include <set> #include <functional> #include <numeric> #include <sstream> #include <stack> #include <map> #include <queue> #define CL(arr, val) memset(arr, val, sizeof(arr)) #define lc l,m,rt<<1 #define rc m + 1,r,rt<<1|1 #define pi acos(-1.0) #define ll __int64 #define L(x) (x) << 1 #define R(x) (x) << 1 | 1 #define MID(l, r) (l + r) >> 1 #define Min(x, y) (x) < (y) ? (x) : (y) #define Max(x, y) (x) < (y) ? (y) : (x) #define E(x) (1 << (x)) #define iabs(x) (x) < 0 ? -(x) : (x) #define OUT(x) printf("%I64d\n", x) #define lowbit(x) (x)&(-x) #define Read() freopen("din.txt", "r", stdin) #define Write() freopen("dout.txt", "w", stdout); #define tr(container, it) for(typeof(container.begin()) it = container.begin(); it != container.end(); it++) #define M 35344 #define N 1888 using namespace std; const ll mod = 1000000007; ll c[N][N]; ll pow2[N]; ll a[N]; int pos[N]; int n,m; void init() { int i,j; for (i = 0; i < N; ++i) c[i][i] = c[i][0] = 1; pow2[0] = 1; pow2[1] = 2; for (i = 2; i < N; ++i) { pow2[i] = (pow2[i - 1]*2)%mod; for (j = 1; j < i; ++j) { c[i][j] = c[i - 1][j - 1] + c[i - 1][j]; c[i][j] %= mod; } } } int main() { // Read(); int i,j; int n,m; init(); scanf("%d%d",&n,&m); for (i = 1; i <= m; ++i) { scanf("%d",&pos[i]); } if (m == n) { printf("1\n"); return 0; } sort(pos + 1,pos + 1 + m); a[1] = pos[1] - 1; for (i = 2; i <= m; ++i) { a[i] = pos[i] - pos[i - 1] - 1; } a[m + 1] = n - pos[m]; ll ans = 1; for (i = 2; i <= m + 1; ++i) { ll tmp = 0; //这里是求将一个序列插入到另一个序列的可能形成的中数 for (j = 1; j <= a[i - 1]; ++j) { tmp = (tmp + c[a[i - 1] -1][j - 1]*c[a[i] + 1][j]%mod); tmp %= mod; } if (tmp != 0) ans = ans*tmp%mod; if (i != m + 1 && a[i] - 1 >= 0) ans = ans*pow2[a[i] - 1]%mod; a[i] = a[i] + a[i - 1]; } printf("%I64d\n",ans); return 0; }
D:
待解决。。。
E:
题意:
给出一颗含有n个节点的树(n - 1条边),n<=5000。 求将一条边断开,然后加到另外一个点上,是的最终形成的还是数,然后求是的所有两点之间的距离和最小。
思路:
不错的题目。 DFS
首先,我们将一条边断开之后会形成两棵树(u->v):A和B,假设A中的节点个数为a,B中的节点个数为b,我们最中要求求的的结果为:(假设枚举的要断开的边的权值为w)
ans = w*a*b + A中两两之间的距离 + B中两两之间的距离 + A中选择一个出点,所有a到B的距离 + B中选择一个出点,所有b到B的距离;
我们这里的关键就是如何确定对于A,B选出最优的出点来将断开的点接到该出点。 我们的做法是首先假设出点为A,或者B的根节点,然后枚举A或者B中的其他点keyp[x]表示以x为出点时要减去的权值,我们只要去keyp[x]最小即可.
#include <iostream> #include <cstdio> #include <cmath> #include <vector> #include <cstring> #include <algorithm> #include <string> #include <set> #include <functional> #include <numeric> #include <sstream> #include <stack> #include <map> #include <queue> #define CL(arr, val) memset(arr, val, sizeof(arr)) #define lc l,m,rt<<1 #define rc m + 1,r,rt<<1|1 #define pi acos(-1.0) #define ll __int64 #define L(x) (x) << 1 #define R(x) (x) << 1 | 1 #define MID(l, r) (l + r) >> 1 #define Min(x, y) (x) < (y) ? (x) : (y) #define Max(x, y) (x) < (y) ? (y) : (x) #define E(x) (1 << (x)) #define iabs(x) (x) < 0 ? -(x) : (x) #define OUT(x) printf("%I64d\n", x) #define lowbit(x) (x)&(-x) #define Read() freopen("din.txt", "r", stdin) #define Write() freopen("dout.txt", "w", stdout); #define sz(a) int((a).size()) #define pb push_back #define all(c) (c).begin(),(c).end() #define tr(c,i) for(typeof((c).begin() i = (c).begin(); i != (c).end(); i++) #define M 137 #define N 5007 using namespace std; typedef vector<int> vi; typedef vector<vi> vvi; typedef pair<int,int> ii; const int inf = 0x7f7f7f7f; const int mod = 1000000007; struct node { int v,w; int next; }g[N*2],idx; int head[N],ct; int son[N]; ll keyp[N]; int f[N]; int n; ll ans; void add(int u,int v,int w) { g[ct].v = v; g[ct].w = w; g[ct].next = head[u]; head[u] = ct++; } void init() { int i; int x,y,z; CL(head,-1); ct = 0; for (i = 1; i < n; ++i) { scanf("%d%d%d",&x,&y,&z); add(x,y,z); add(y,x,z); } } void getSon(int u,int fa) { int i; son[u] = 1; f[u] = fa; for (i = head[u]; i != -1; i = g[i].next) { int v = g[i].v; if (v == fa) continue; getSon(v,u); son[u] += son[v]; } } ll oneSum,twoSum,sum; void solve(int u,int fa,int num) { int i; for (i = head[u]; i != -1; i = g[i].next) { int v = g[i].v; int w = g[i].w; if (v == fa) continue; if (u == idx.w && v == idx.v) continue; //假设一根节点为出点的权值 oneSum += (ll)w*son[v]; //A或者B中两两之间的距离 sum += (ll)w*son[v]*(num - son[v]); //得到最小的要减去的数 keyp[v] = (ll)keyp[u] + (ll)(num - son[v])*w - (ll)son[v]*w; twoSum = min(twoSum,keyp[v]); solve(v,u,num); } } void dfs(int u,int fa) { int i,j; for (i = head[u]; i != -1; i = g[i].next) { int v = g[i].v; int w = g[i].w; if (v == fa) continue; for (j = u; j != 0; j = f[j]) son[j] -= son[v]; sum = 0; //w*a*b sum = (ll)w*(n - son[v])*son[v]; idx.w = u; idx.v = v; oneSum = 0; twoSum = 0; keyp[1] = 0; solve(1,0,son[1]); //获得出点后,然后计算到B的权值 sum += (ll)(oneSum + twoSum)*son[v]; oneSum = 0; twoSum = 0; keyp[v] = 0; solve(v,u,son[v]); sum += (ll)(oneSum + twoSum)*son[1]; if (ans == -1 || ans > sum) ans = sum; for (j = u; j != 0; j = f[j]) son[j] += son[v]; dfs(v,u); } } int main() { scanf("%d",&n); init(); getSon(1,0);//获得每个节点的儿子节点数,和父亲节点数 ans = -1; dfs(1,0);//枚举每一个边断开 printf("%I64d\n",ans); }