5 processus de planification des tâches de la CPU

Question.

Il y a 5 CPU et N nombre de tâches dans la file. Vous devez utiliser un nombre minimal de processeurs pour traiter les tâches.

Une tâche est au format [heure d’arrivée, heure du traitement de la tâche].

Remarque:

  1. Vous ne pouvez utiliser que 5 CPU au maximum. Si cela n’est pas possible dans 5 UC, imprimez -1.

  2. Le temps de traitement de la tâche ne doit pas être supérieur à 10, c’est-à-dire (temps d’attente dans la queue + temps de traitement de la tâche) <= 10.

  3. Si le traitement de la tâche en cours nécessite plus de 10 secondes dans la CPU actuelle, vous pouvez passer à une autre CPU et vérifier s’il est possible de traiter la tâche dans un délai <= 10.

  4. S’il n’est pas possible de traiter la tâche dans <= 10 ou au plus 5 CPU, imprimez -1.

Contraintes.

0 <= heure d'arrivée <= 500

1 <= heure de traitement de la tâche <= 10

0 <= N <= 500

Vous ne pouvez utiliser que la bibliothèque iostream. Aucune STL n’est autorisée.

Temps: 3 secondes pour les cas de test T

Par exemple:-

Consortingbution

3

1 6

2 7

3 1

Sortie

2

Explication:

3 – N

1 6 – la première tâche arrive dans CPU0 à l’heure 1 et part à l’heure 7 (1 + 6). CPU utilisés = 1.

2 7 – la deuxième tâche arrive dans CPU0 à l’heure 2 et attend 5 secondes dans la queue; le temps de traitement global est donc 5 + 7> 10. Elle est donc déplacée vers CPU1. CPU utilisées = 2.

3 1 – la troisième tâche arrive. il peut aller à CPU0 ou CPU1, le temps de traitement étant respectivement de 5 ( (7-3) + 1 ) et de 7 ( (9-3) + 1 ) secondes. CPU utilisées = 2.

CPU1 est un nouveau processeur. Donc, la tâche 2 sera complétée en 9 (2 + 7) secondes sans aucun délai d’attente dans la queue.

Mon approche:

  1. Initialement, cela a été pensé comme une variante du minimum train-platform problem . Mais c’était faux.

  2. J’ai essayé une approche greedy , mais cela a donné -1 pour des solutions valables.

CODE:

 #include  using namespace std; #define MAXN 500 int N; int arr[MAXN]; int len[MAXN]; int main(){ ios_base::sync_with_stdio(false); cin.tie(NULL); cin>>N; for(int i=0;i> arr[i] >> len[i]; } int exit_time_of_task_in_CPUs[5]={0}; int cpus_used_so_far=1; int min_processing_time; int min_processing_time_idx; for(int task_idx=0; task_idx<N; task_idx++){ min_processing_time = INT_MAX; min_processing_time_idx = -1; // finds the CPU which can process task in minimum time. for(int i=0; i<cpus_used_so_far ; i++){ int processing_time = 0; int time_in_queue = exit_time_of_task_in_CPUs[i] - arr[task_idx]; if( time_in_queue < 0 ) // ie processor is free before arrival time_in_queue = 0; processing_time = time_in_queue + len[task_idx]; if( processing_time <=10){ if(processing_time < min_processing_time){ min_processing_time = processing_time; min_processing_time_idx = i; } } } if( min_processing_time_idx == -1){ // No Existing CPU can solve this task. // Check if spawning a new CPU is possible. if (cpus_used_so_far+1 <= 5){ //spawn a new CPU cpus_used_so_far++; int new_cpu_index = cpus_used_so_far - 1; // last CPU, converting to zero index exit_time_of_task_in_CPUs[new_cpu_index] = arr[task_idx] + len[task_idx] ; }else{ cpus_used_so_far = -1; // not possible to spawn a new CPU, break; //and you can't process with existing CPUs } }else{ // Possible to handle with existing CPUs exit_time_of_task_in_CPUs[min_processing_time_idx] = arr[task_idx] + min_processing_time; } } cout << cpus_used_so_far <<endl; } 
  1. Essayer l’approche de memoization .

L’approche récursive résout le problème, mais le délai est dépassé. (Pas étonnant)

Quel est le moyen possible de sauvegarder l’état de int , int , int[] ?

Je suis également ouvert à une solution itérative pour la même chose.

CODE:

 #include  using namespace std; #define MAXN 500 int min_cpus_used = 6; int N; int arr[MAXN]; int len[MAXN]; void recurse( int task_idx, int cpus_used_so_far, int exit_time_of_task_in_CPUs[] ){ if( task_idx == N-1){ min_cpus_used = min( min_cpus_used, cpus_used_so_far); return ; } if( cpus_used_so_far >= min_cpus_used ){ return ; //optimization } for(int i=0; i<cpus_used_so_far ; i++){ int processing_time = 0; int time_in_queue = exit_time_of_task_in_CPUs[i] - arr[task_idx]; if( time_in_queue < 0 ) // ie processor is free before arrival time_in_queue = 0; processing_time = time_in_queue + len[task_idx]; // try with existing CPUs if( processing_time <=10){ int prev = exit_time_of_task_in_CPUs[i]; exit_time_of_task_in_CPUs[i] = arr[task_idx] + processing_time; recurse( task_idx + 1 , cpus_used_so_far , exit_time_of_task_in_CPUs ); // can we optimize passing array exit_time_of_task_in_CPUs[i] = prev; } // try with new CPU if (cpus_used_so_far+1 >N; for(int i=0;i> arr[i] >> len[i]; } int exit_time_of_task_in_CPUs[5]={0}; recurse(0, 1, exit_time_of_task_in_CPUs); if(min_cpus_used==6){ cout<< -1 <<endl; }else{ cout << min_cpus_used <<endl; } } 

Aidez-moi à réfléchir à l’optimisation de cette solution.

PS: Cela n’a rien à voir avec la planification du processeur.

Résumé : bruteforce the answer (nombre maximal de processeurs), sortingez les tâches par heure de début, affectez-les aux processeurs dans cet ordre avec DP avec profil (profil est le moment le plus proche lorsqu’un processeur spécifique est disponible; ignore l’ordre des processeurs).

La solution est plutôt sophistiquée. N’hésitez pas à demander des éclaircissements.

Idée 1 : force brutale sur le nombre maximal de CPU et force brute (ou appliquons la recherche binary: si A CPU suffit, alors A+1 CPU suffisent également). Nous avons maintenant un nombre fixe de processeurs et nous devons résoudre un problème de type oui / non: est-il possible d’organiser l’exécution des tâches de telle ou telle manière? Comme nous avons 5 processeurs maximum, cette idée peut être mise en œuvre avec une simple boucle externe.

Idée 2 : il existe une solution qui lance le traitement de toute tâche à un moment entier.

Preuve: prenez n’importe quelle solution, choisissez la tâche la plus à gauche pour laquelle cette condition ne se vérifie pas, et notez que vous pouvez la déplacer plus à gauche. Cela réduit la sum totale des positions de toutes les tâches. Maintenant, choisissons la solution qui minimise cette valeur. Il ne peut pas avoir de tâches qui commencent à des heures non entières.

Idée 3 : il existe une solution avec la condition suivante sur chaque CPU: pour chaque tentative de test des tâches A et B telles que A est exécutée juste avant B , A apparaît dans la liste des tâches sortingées antérieures à B (sortingées par leur heure d’arrivée / de création) ).

Preuve: prenez une solution arbitraire et des tâches de type bulle sur chaque CPU. Nous devons prouver que l’échange de deux tâches adjacentes ne rend pas la solution incorrecte. Considérez les tâches A et B telles que A va après B dans la liste sortingée. Cela signifie que son heure de création n’est pas supérieure à celle de B ; B existe donc au moment où nous avons commencé à traiter A Cela signifie également que l’heure de fin souhaitée de A n’est pas supérieure à l’heure de création de B , donc au moment où B est terminé, nous sums toujours autorisés à terminer A Ainsi, échanger leur ordre d’exécution ne rend pas la solution incorrecte et nous sums libres de le faire.

Idée 4 : il est possible de créer une solution en commençant par des plans d’exécution vides pour tous les processeurs, puis en ajoutant les tâches une par une à certains plans, en commençant par la plus récente et en terminant par la dernière tâche. Nous ajoutons une tâche au temps le plus court possible pour le démarrer sur une CPU donnée.

Preuve: prenez une solution arbitraire avec la propriété d’idée 3. Notez que le plan d’exécution de chaque CPU est une sous-séquence de la liste des tâches sortingée. Cela signifie que chaque tâche de la liste sortingée peut être affectée à une CPU et qu’après avoir exécuté l’algorithme de l’idée 4, nous disposerons d’un plan d’exécution. Ce ne sera “pas pire” que notre solution d’origine: les positions de toutes les tâches ne seront pas supérieures aux positions des tâches du plan d’origine. Ce plan est donc aussi une solution.

Idée 5 : effectuez un retour arrière récursif pour rechercher la répartition des tâches entre les processeurs. Nous soaps déjà dans quel ordre ils doivent être ajoutés aux CPU (ordre de création). Gardez une trace du premier temps disponible sur chaque CPU et affectez la force brute de la tâche suivante à chaque étape de la recherche. Par exemple, pour des tâches de longueur 1, 5, 2, 9, 3 commençant à zéro, l’affectation peut être:

 Task start: 0 0 0 0 0 Task length: 1 5 2 9 3 Assigned CPU: ABBAB 

Idée 6 : append de la mémoisation (convertir le retour arrière en programmation dynamic). Notez que nous ne nous soucions pas vraiment si le premier temps disponible sur le processeur est inférieur à l’heure de début X de la tâche suivante et s’il ne peut pas être supérieur à X+10 . Nous avons donc 11 possibilités pour chaque CPU, soit 11^5=161051 possibilités pour 5 CPU. C’est le “profil” de notre PDD. Puisque nous ne nous soucions pas de l’ordre des processeurs, les réponses pour les profils qui diffèrent par permutation sont les mêmes. Nous ne pouvons donc considérer que 3003 profils différents (c’est \choose{10+5}{5} ). Une autre variable de notre état est le numéro de la tâche suivante (500 possibilités), et nous avons également 5 transitions de chaque état (quelle CPU utiliser). Cela fait 3003*500*5 ~ 8m transitions, ce qui n’est pas un grand nombre; nous pouvons donc heureusement l’écraser sous notre boucle externe de l’idée 1. Bien sûr, il serait préférable de précalculer les transitions entre les profils, mais cela peut aussi être possible d’aller sans cela.