意识到比赛的时候已经过去了一个多小时;心情低躁;于是成功弃赛。。
看完这题一直在想O(n)的方法,最后只想到一个O(nlogn)的方法。我们对于每个节点都以左端点建一棵treap,然后用右端点去找最小值即可。然而这样莫名被卡掉2个点。(treap的常数?!)
题解的作法是排序+二分,方法也差不多,只需维护一个后缀最小值即可。
另外还有用链表的O(n)作法,去看KsCla的博客吧。
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define maxn 1000010
#define oo 0x7fffffff
#define _min(a, b) (((a) < (b)) ? (a) : (b))
using namespace std;
int n, x, cnt, ans = oo;
struct Data{
int L, R, cost, day;
}plan[maxn];
struct Treap{
Treap *L, *R;
int val, cost, Min, fix;
int Lmin(){return L ? L->Min : oo;}
int Rmin(){return R ? R->Min : oo;}
void Up(){
Min = _min(cost, _min(Lmin(), Rmin()));
}
}Node[maxn], *Root[maxn];
Treap *NewTnode(int val, int cost){
Node[cnt].L = Node[cnt].R = NULL;
Node[cnt].val = val;
Node[cnt].cost = Node[cnt].Min = cost;
Node[cnt].fix = rand();
return Node+cnt++;
}
void Treap_L_Rot(Treap *&a){
Treap *b = a->R;
a->R = b->L;
b->L = a;
a = b;
a->L->Up();
a->Up();
}
void Treap_R_Rot(Treap *&a){
Treap *b = a->L;
a->L = b->R;
b->R = a;
a = b;
a->R->Up();
a->Up();
}
void Treap_Insert(Treap *&p, int val, int cost){
if(!p) p = NewTnode(val, cost);
else if(val == p->val){
p->cost = _min(p->cost, cost);
p->Min = _min(p->Min, cost);
}
else if(val < p->val){
Treap_Insert(p->L, val, cost);
p->Min = _min(p->Min, cost);
if(p->L->fix < p->fix) Treap_R_Rot(p);
}
else{
Treap_Insert(p->R, val, cost);
p->Min = _min(p->Min, cost);
if(p->R->fix < p->fix) Treap_L_Rot(p);
}
}
int Treap_Work(Treap *p, int val){
if(!p) return oo;
if(p->val > val) return _min(_min(p->Rmin(), p->cost), Treap_Work(p->L, val));
else return Treap_Work(p->R, val);
}
int main(){
scanf("%d%d", &n, &x);
for(int i = 1; i < x; i++) Root[i] = NULL;
for(int i = 1; i <= n; i++){
scanf("%d%d%d", &plan[i].L, &plan[i].R, &plan[i].cost);
plan[i].day = plan[i].R - plan[i].L + 1;
if(plan[i].day < x) Treap_Insert(Root[plan[i].day], plan[i].L, plan[i].cost);
}
for(int i = 1; i <= n; i++){
if(plan[i].day >= x) continue;
int temp = Treap_Work(Root[x-plan[i].day], plan[i].R);
if(temp == oo) continue;
ans = _min(ans, plan[i].cost + temp);
}
if(ans == oo) ans = -1;
printf("%d\n", ans);
return 0;
}
#include
#include
#include
#include
#include
#include
#define maxn 1000010
#define oo 0x7fffffff
using namespace std;
int n, x, Min[maxn], ans = oo;
struct Data{
int L, R, day, cost;
bool operator < (const Data& Q) const{
if(day == Q.day) return L < Q.L;
return day < Q.day;
}
}plan[maxn];
bool check(int a, int b){
return plan[a].day + plan[b].day > x || (plan[a].day + plan[b].day == x && plan[b].L > plan[a].R);
}
int main(){
scanf("%d%d", &n, &x);
for(int i = 1; i <= n; i++){
scanf("%d%d%d", &plan[i].L, &plan[i].R, &plan[i].cost);
plan[i].day = plan[i].R - plan[i].L + 1;
}
sort(plan+1, plan+n+1);
for(int i = n, j = n; i > 0; i = j){
while(plan[j].day == plan[i].day){
if(j == i) Min[j] = plan[j].cost;
else Min[j] = min(plan[j].cost, Min[j+1]);
j --;
}
}
for(int i = 1; i <= n; i++){
if(plan[i].day >= x) continue;
int L = 0, R = n + 1;
while(L + 1 < R){
int mid = (L + R) >> 1;
if(check(i, mid)) R = mid;
else L = mid;
}
if(R <= n && plan[i].day + plan[R].day == x)
ans = min(ans, plan[i].cost + Min[R]);
}
if(ans == oo) ans = -1;
printf("%d\n", ans);
return 0;
}
这题是一道状压DP的好题。看了题解我才能做出来。
我们考虑将第i的字符串的第j位换掉,为了做到这个,我们可以直接将第i的字符串的第j位换成一个合法的字符,因为只有20个串,有26个字符,所以肯定可以。这样只影响自己的状态,将这位变成1,我们记当前已经独特的为1。另外的转移就是可以将其他串这一位和它一样的换掉,然后这样除了最贵的那个全换就行了,也影响了其他的状态。为了避免重复转移,确定的转移次序就是最后一个0(套路同2016 愤怒的小鸟)。然后就是一大堆的预处理了(见代码)。
时间复杂度 O(m2n+n2m) 。
#include
#include
#include
#include
#include
#include
#define maxn 22
using namespace std;
int n, m, start;
char str[maxn][maxn];
int cost[maxn][maxn], sum[maxn][maxn], Max[maxn][maxn], sam[maxn][maxn];
int dp[1<int main(){
scanf("%d%d", &n, &m);
for(int i = 1; i <= n; i++)
scanf("%s", &str[i]);
for(int i = 1; i <= n; i++)
for(int j = 1; j <= m; j++)
scanf("%d", &cost[i][j]);
for(int i = 1; i <= n; i++){
bool easy = false;
for(int j = 1; j <= m; j++){
bool f = true;
for(int k = 1; k <= n; k++){
if(str[i][j-1] == str[k][j-1]){
sum[i][j] += cost[k][j];
sam[i][j] |= (1<<(k-1));
Max[i][j] = max(Max[i][j], cost[k][j]);
if(i != k) f = false;
}
}
if(f) easy = true;
}
if(easy) start |= (1 << (i-1));
}
memset(dp, -1, sizeof(dp));
dp[start] = 0;
for(int s = start; s < (1<1; s++){
if(dp[s] == -1) continue;
int low;
for(int i = 1; i <= n; i++)
if(!((1<<(i-1)) & s)){
low = i;
break;
}
int ns;
for(int i = 1; i <= m; i++){
ns = s | (1<<(low-1));
if(dp[ns] == -1) dp[ns] = dp[s] + cost[low][i];
else dp[ns] = min(dp[ns], dp[s] + cost[low][i]);
ns = s | sam[low][i];
if(dp[ns] == -1) dp[ns] = dp[s] + sum[low][i] - Max[low][i];
else dp[ns] = min(dp[ns], dp[s] + sum[low][i] - Max[low][i]);
}
}
printf("%d\n", dp[(1<1]);
return 0;
}
网络流的裸题,初三打ACM的时候做过的,当时我的两个队友忘了拆点,拆完点最后WAWAWA,忘了考虑“人间蒸发”的情况。
其他的就是水题,从源向汇流,按图连边,判断能否满流即可,输出方案的话,由于直接最大流,于是发现每条边流了多少实际就是多少,于是反向边的容量就是方案。
多路增广有点不熟。
#include
#include
#include
#include
#include
#include
#define maxn 105
#define maxm 205
#define INF 0x7fffffff
using namespace std;
int n, m, s, t, sumA, sumB;
int a[maxn], b[maxn], path[maxn][maxn];
int head_p[maxn<<1], level[maxn<<1], iter[maxn<<1], q[maxn<<1], cur = -1;
struct Adj {int next, cap, obj;} Edg[(maxn*6)+(maxm*4)];
void Insert(int a, int b, int c){
cur ++;
Edg[cur].next = head_p[a];
Edg[cur].obj = b;
Edg[cur].cap = c;
head_p[a] = cur;
}
int Dinic(int now, int f){
if(now == t || !f) return f;
int ret = 0;
for(int &i = iter[now]; ~ i; i = Edg[i].next){
int v = Edg[i].obj, c = Edg[i].cap;
if(c && level[v] > level[now]){
int d = Dinic(v, min(f, c));
Edg[i].cap -= d;
Edg[i^1].cap += d;
f -= d;
ret += d;
if(!f) break;
}
}
return ret;
}
bool bfs(){
memset(level, -1, sizeof(level));
level[s] = 0;
q[0] = s;
int head = 0, tail = 0;
while(head <= tail){
int now = q[head++];
for(int i = head_p[now]; ~ i; i = Edg[i].next){
int v = Edg[i].obj, c = Edg[i].cap;
if(c && level[v] == -1){
level[v] = level[now] + 1;
q[++tail] = v;
}
}
}
return level[t] != -1;
}
int Max_flow(){
int flow = 0;
while(bfs()){
memcpy(iter, head_p, sizeof(iter));
flow += Dinic(s, INF);
}
return flow;
}
void Print(){
for(int i = 2; i <= n+1; i++)
for(int j = head_p[i]; ~ j; j = Edg[j].next){
int v = Edg[j].obj;
if(v > n && v <= (n << 1 | 1)) path[i-1][v-n-1] = Edg[j^1].cap;
}
for(int i = 1; i <= n; i++){
for(int j = 1; j <= n; j++)
printf("%d ", path[i][j]);
printf("\n");
}
}
int main(){
scanf("%d%d", &n, &m);
for(int i = 1; i <= n; i++){
scanf("%d", &a[i]);
sumA += a[i];
}
for(int i = 1; i <= n; i++){
scanf("%d", &b[i]);
sumB += b[i];
}
s = 1; t = s + (n << 1) + 1;
memset(head_p, -1, sizeof(head_p));
for(int i = 1; i <= n; i++){
Insert(s, s+i, a[i]);
Insert(s+i, s, 0);
}
for(int i = 1; i <= n; i++){
Insert(s+n+i, t, b[i]);
Insert(t, s+n+i, 0);
}
for(int i = 1; i <= n; i++){
Insert(s+i, s+n+i, INF);
Insert(s+n+i, s+i, 0);
}
int x, y;
for(int i = 1; i <= m; ++i){
scanf("%d%d", &x, &y);
Insert(s+x, s+n+y, INF);
Insert(s+n+y, s+x, 0);
Insert(s+y, s+n+x, INF);
Insert(s+n+x, s+y, 0);
}
if(sumA == sumB && sumB == Max_flow()){
puts("YES");
Print();
}
else puts("NO");
return 0;
}
虽然没有参赛,但还是感觉自己好菜,整天想数据结构,应该更加重视链表,二分等基础知识,还有重视dp的训练,熟悉套路后dp题才好做。
温柔正确的人总是难以生存,因为这世界既不温柔,也不正确。