http://acm.sjtu.edu.cn/OnlineJudge/problem/4020
一上手就来了一个删点 排序+DFS.... 虽然正确性没问题 但是超时 只有60分. 主要在于不知道怎么减少搜索量
思路就是删除一些肯定不能在的点, 然后经过条件判断 DFS地去搜索最长的路径
#include <iostream> #include <vector> #include <algorithm> #include <cstring> #include <stack> using namespace std; int n; int data[1000+10]; bool vis[1000+10]={false}; struct Point { int oriPos; int num; int todo; int vid; int done; int len; Point(int x,int y){ oriPos = x; num = y; todo = oriPos - num; vid = -1; done = 0; len = 0; } }; vector<Point> v; bool cmp_point(const Point& a , const Point& b){ return a.num < b.num; } void init(){ v.clear(); for (int i = 1; i <= n; ++i) { cin>>data[i]; if(data[i]<=i){ Point t(i,data[i]); v.push_back(t); } } sort(v.begin(),v.end(),cmp_point); memset(vis,false,sizeof(vis)); } //必须dfs int build(){ for (int i = 0; i < v.size(); ++i) { //cout<<v[i].oriPos<<","<<v[i].num<<","<<v[i].todo<<endl; v[i].vid = i; } int ans = 0; stack<Point> s; Point start(0,0); s.push(start); while(!s.empty()){ Point cur = s.top(); s.pop(); if(cur.vid >= 0) vis[cur.vid] = true; for (int j = cur.vid+1; j < v.size(); ++j) { if(cur.vid==-1 or (v[j].oriPos > v[cur.vid].oriPos and v[j].todo-cur.done >= 0 and v[j].num!=v[cur.vid].num)){ vis[j] = true; Point next(0,0); next.oriPos = v[j].oriPos; next.num = v[j].num; next.len = cur.len+1; next.done = v[j].todo; next.vid = j; ans = max(next.len,ans); s.push(next); } } } return ans; } int main(int argc, char const *argv[]) { while(cin>>n){ init(); cout<<build()<<endl; } return 0; }
正确的方法呢还是要DP的.
这里要看题里的暗示, 他强调的是从n个中擦去某几个 .
这个某几个暗示最后的dp用二维+for遍历
就是说 我们的dp[i][j]要表示为 前i个数, 擦去某j个 剩余的符合条件的数的个数
最后我们输出 max(dp[n][k]) k = 1....n; 即可
转移方程如下
dp[0][0] = 0; for (int i = 1; i <= n; ++i){ dp[i][0] = ( data[i]==i )? dp[i-1][0]+1 : dp[i-1][0]; for (int j = 1; j < i; ++j){ if(data[i]==((i-1)-j)+1){ dp[i][j] = dp[i-1][j] + 1; }else{ dp[i][j] = max(dp[i-1][j-1],dp[i-1][j]); } } dp[i][i] = 0; }
解释一下
我们对于dp[i][j]要分j的情况进行讨论
1. j=0 那么此时dp[i][0] 有两种情况,
如果data[i] = i 表示data[i]本身就在原位置 那么符合条件的数的个数就是之前的i-1个里符合条件的数的个数+1, dp[i][0] = dp[i-1][0]+1
否则 和之前的一样
2.j=i dp[i][i]=0 这个很好理解 前i个数字擦去i个 最后符合条件的肯定是0个....因为已经没有数了
3.j = 1,2,3,...,i-1
也有两种情况, 一个是如果我们的data[i]恰好是 前i-1个数字里擦去了j个 之后的那个位置,就累加1
比如 i = 5 , j =2 时 如果前4个数里,我们擦去了2个 那么还剩两个合法的, 我们在后面附加一个数的话 他的位置是3 如果我们附加的这个数恰好就是3的话 那么前5个数里,就有3个合法的了
所以此时 dp[i][j] = dp[i-1][j] +1
如果不能满足这个条件
那么考虑 前i个数里去掉某j个 有两种去掉的方法 第一种 所有擦去的数 都是i-1个里面的 那么就是 dp[i-1][j]
第二种 有j-1个是前i-1个里面的 剩下的那个就是第i个元素 所以是 dp[i-1][j-1]
两者选最大的即可.
代码如下(注释很多 不太清晰)
#include <iostream> #include <vector> #include <algorithm> #include <cstring> #include <stack> #include <cstdio> using namespace std; const int MaxN = 1000+10; int n; int data[MaxN]; int dp[MaxN][MaxN]={0}; //dp[i][j]表示 前i个数,去除某j个的时候 剩余的数中的符合条件的数个数 void init(){ for (int i = 1; i <= n; ++i) { scanf("%d",&data[i]); } //dp的初始化 } int build(){ //dp[i][j]表示 前i个数,去除某j个的时候 剩余的数中的符合条件的数个数 dp[0][0] = 0; for (int i = 1; i <= n; ++i){ //分成三段处理 //第一段 j=0 dp[i][0] = ( data[i]==i )? dp[i-1][0]+1 : dp[i-1][0];//如果第i个数是i的话 要更新+1 //第二段 j=1,2,3...i-1 for (int j = 1; j < i; ++j){ //i中减去了j个 所以 还剩 i-j个 // if(data[i]==(i-1)-j + 1) //从前i-1个里面减去了j个数 我们的位置紧在之后+1 // dp[i][j] = dp[i-1][j] + 1; // else//不能放就从前面的两个状态选择一个最大的 // //一个可能是前i-1个中 擦去j-1个 留住i // //另一个可能是从前i-1个中 擦去j个 再擦去i // dp[i][j] = max(dp[i-1][j-1],dp[i-1][j]); if(data[i]==((i-1)-j)+1){ dp[i][j] = dp[i-1][j] + 1; }else{ dp[i][j] = max(dp[i-1][j-1],dp[i-1][j]); } } //第三段 j=i dp[i][i] = 0;//前i个 擦去了i个 肯定结果是0 } int ans = 0; //注意一定要从0开始遍历 否则有漏洞 比如 n=4 时: 1 2 3 4 这种情况 for (int i = 0; i <= n; ++i) { ans = max(ans,dp[n][i]); } return ans; } int main(int argc, char const *argv[]) { while(cin>>n){ init(); cout<<build()<<endl; } return 0; } /* void init1(){ //v.clear(); for (int i = 1; i <= n; ++i) { //cin>>data[i]; scanf("%d",&data[i]); // if(data[i]<=i){ // Point t(i,data[i]); // v.push_back(t); // } } //sort(v.begin(),v.end(),cmp_point); //memset(vis,false,sizeof(vis)); } int build2(){ for (int i = 0; i < v.size(); ++i) { //cout<<v[i].oriPos<<","<<v[i].num<<","<<v[i].todo<<endl; v[i].vid = i; } int ans = 0; stack<Point> s; Point start(0,0); s.push(start); while(!s.empty()){ Point cur = s.top(); s.pop(); if(cur.vid >= 0) vis[cur.vid] = true; for (int j = cur.vid+1; j < v.size(); ++j) { if(v.size()-1-cur.vid + cur.len < ans) break; if(cur.vid==-1 or (v[j].oriPos > v[cur.vid].oriPos and v[j].todo-cur.done >= 0 and v[j].num!=v[cur.vid].num)){ vis[j] = true; Point next(0,0); next.oriPos = v[j].oriPos; next.num = v[j].num; next.len = cur.len+1; next.done = v[j].todo; next.vid = j; ans = max(next.len,ans); s.push(next); } } } return ans; } */ //1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 //20 4 6 2 14 10 20 11 9 16 5 13 2 7 18 19 1 3 17 9 15 //5 2 7 1 3 17 8 15 // 5 7 8 dfs.... //20 4 6 2 14 10 20 11 9 16 5 13 2 7 18 19 1 3 17 9 15 /* struct Point { int oriPos; int num; int todo; int vid; int done; int len; Point(int x,int y){ oriPos = x; num = y; todo = oriPos - num; vid = -1; done = 0; len = 0; } }; for (int i = 0; i < v.size() ; ++i){ int len = 1;//头 int done = v[i].todo;//左面已经减少了多少个数字 int cur = i;//当前指针 for (int j = i+1; j < v.size(); ++j){ if(v[j].oriPos > v[cur].oriPos and v[j].todo-done >= 0 and v[j].num!=v[cur].num){ cur = j; done = v[j].todo; len++; } } ans = max(ans,len); } */